0%

PMP

RISC-V架构提供了一种PMP物理内存保护机制,用于隔离M模式与S/U模式下的内存访问。只有M模式才有权限配置PMP。

PMP包含几组(通常是8到16个)地址寄存器以及相应的配置寄存器,这些配置寄存器可以授予或拒绝S/U模式的读、写和执行权限。

PMP同时也能保护内存映射I/O (MMIO),M模式可信固件可以通过配置PMP来约束处理器对外设I/O的访问。

配置cpu csr
物理内存保护设置寄存器(PMPCFG)
物理内存保护地址寄存器(PMPADDR)
PMP 共实现了 8 个地址寄存器 pmpaddr0-pmpaddr7,存放表项的物理地址

缺点:

仅适用于单一核心, 无法与其他核心同步, 需要添加同步控制.
只能够对内存进行保护

GPU/PPU/NPU never check

IOPMP

ANDES thead 安全扩展

进riscv 主线进展: The IOPMP specification is still under discussion, and its ratification target date is Q4, 2023.

  • A source-ID represents a bus master or a group of bus masters with the same permission.
  • A bus master has one, but could be the same as another.
  • A bus master with multi-channel, multi-VM or multi-mode may need 1+ SIDs.

 IOPMP被用来规范请求。谁可以访问哪些数据?
 Source-ID, entry, ports, matching rules and violation responses.

总线上的其他主设备同样需要对内存的访问进行保护,也就是外设需要增加IOPMP。IOPMP可以像PMP一样定义访问权限,他会检查从总线或者主设备过来的读、写传输是否符合权限访问规则,只有合法的读、写请求才能进一步传输到目标设备上。通常有3种方法来连接IOPMP:

请求端连接IOPMP

在每个主设备和总线之间增加一个IOPMP,类似RISC-V的PMP。不同的主设备需要分别新增一个IOPMP,IOPMP之间互相独立。这种设计较为简单,也提供了灵活性,但IOPMP不能在主设备之间共享。如下图所示:

 

目标端连接IOPMP

目标端的IOPMP需要对不同主设备发来的传输进行区分,这就需要每个主设备的访问请求都要附带额外的Master ID。如下图所示:

请求端和目标端级联IOPMP

在复杂的SoC系统中,往往存在IOPMP级联的使用情况。典型的场景就是RISC-Cores自身带有PMP,处理器并不需要目标端的IOPMP再次对他的访问进行过滤。通用的处理方法是,在IOPMP的表项里取消对CPU访问的约束,但缺点是这会占用IOPMP表项,也会影响效率。为了提高IOPMP级联下的访问效率,IOPMP需要提供一种机制来直通部分主设备的访问,比如提供可配置的以直通模式访问的主设备列表。

ANDES/THEAD

sifive shield

SiFive WorldGuard是一个用于隔离代码执行和数据保护的细粒度安全模型。SiFive Worldguard提供SoC级别的信息控制,具有先进的隔离控制,基于每个世界的多个权限级别。SiFive WorldGuard为多域安全提供了核心驱动和进程ID驱动的模式,为核心、缓存、互连、外围和内存提供数据保护。


在多核处理器中,如上图所示,世界ID标记被用来将进程相互隔离,以确保保护和隔离的执行。在SoC内部,WID标记从核心延伸到缓存、互连、外设、总线master、DMA区域和存储器。应用程序或操作系统环境可以在一个高性能的多核系统中被隔离和保护。对于单核更常见的嵌入式系统,PID驱动的世界ID在用户和机器模式之间保护和隔离执行。

这个解决方案并没有取代RISC‑V核心的PMP机制(适用于单核的存储器),也没有取代存储器管理单元(MMU),而是将它们扩展到有其他发起者的多核系统,并提供更强的安全性

对于更复杂的多核平台,PMP和特权模式方法有一个主要的限制。由于特权模式和PMP作用域仅限于其核心和软件,它们只能控制单个核心的内存映射区域。特权管理寄存器属于那个单一的内核,所以它们的值和产生的控制既不与其他内核共享,也不和其他核心协同,因此需要复杂的同步工作。在这种情况下,使用受信任的核心可能是首选。

PMP方法的另一个限制适用于”fractured” 物理地址内存map。在这种情况下,所需的PMP条目数可能会变得太大,无法接受。SiFive WorldGuard解决方案是一个很好的选择。


发起方(绿色)都配备了一个wgMarker,标记他们的出站请求

受信核心,通过发送标有受信任的WID的请求来配置wgMarkers(蓝色)和wgCheckers(粉红色)。

Any marked transaction goes through the wg-aware bus and blocks (e.g., L2 cache) until the targeted wgChecker is reached

The wgChecker (regardless of complexity) determines if the WID and related meta-data (access type) are consistent with the rules it contains; if it matches, the transaction is authorized

请求从其WID信息中释放出来,被发送到资源(中断控制器、调试模块)或出站端口(内存端口、外围端口、系统端口)。

VirtualZone

玄铁C系列处理器的安全拓展

该扩展主要基于RISC-V架构提供的PMP保护机制和多层特权模型,虚拟出多个相互隔离的可执行域(Zone),从而实现了RISC-V架构上的可信执行环境(TEE),并保护Zone内的软硬件信息,包括软件、内存、外设、I/O等免受其他Zone的非法访问。处理器资源包括Cache、中断、内存、代码执行等经过隔离之后,处理器将分时地运行在不同的Zone内,配合SoC其他的保护机制如IOPMP,共同构建一个基于软硬件协同工作的安全系统。

虽然RISC-V架构的处理器具备物理内存保护、多层权限模型、内存管理单元等技术来支持可信执行环境功能的实现,但处理器仍然需要支持其他安全规范才能创建完全可用的安全执行环境。为了满足TEE(Trusted Execution Environment)的隔离要求,玄铁C系列处理器在RISC-V架构基础上进行了安全扩展。 该系列处理器在软件的协调下可以虚拟出多个执行域(Zone),每个Zone增加了域标识,也就是Zone ID,整体架构如Figure 2所示。每个Zone可以独立地运行各自的操作系统以及基于该操作系统的应用程序。操作系统运行在超级用户特权模式,应用程序运行在普通用户特权模式。处理器根据需要在不同的Zone里切换运行。当处理器切换到某一Zone运行时,他将实时占用整个物理核,并且处理器的域标识也将同时被更新成相应执行域的标识。Zone的切换由运行在最高模式(机器模式)下的可信固件(TF)来完成。

Zone之间访问隔离通过PMP(Physical Memory Protection)实时切换来实现,PMP是RISC-V特权ISA的一部分,用于隔离机器模式和超级用户模式/普通用户模式之间的物理地址访问,玄铁处理器的L1/L2 Cache同时也受PMP保护。

当硬件线程从一个Zone切换到另一个Zone时,PMP配置同时也需要切换。M模式可信固件需要先保存当前Zone的PMP配置,然后载入下一个即将切换的Zone的PMP配置,完成对内存和内存映射I/O (MMIO)访问权限的切换。

当多个Zone需要共享内存时,可以将需要共同访问的内存区域的访问权限同时授予给多个Zone,也就是将该块内存的允许访问权限写到每个Zone的PMP配置表里,PMP表会在Zone切换时由可信固件进行更新。Figure 3是一个典型的多个Zone的PMP配置图, SHM区域是Zone间允许共同访问的共享内存区域。

机器模式可以通过PMP的锁定功能将机器模式的访问限制在有限的区域内,比如只允许机器模式访问、执行划分给可信固件的内存区域,以减少受到对机器模式攻击的影响,也就是Supervisor Memory Access Prevention(SMAP)和Supervisor Memory Execution Prevention(SMEP),从而增加对关键信息的保护。

下图是玄铁处理器采用PMP、IOPMP来构建安全SoC的系统参考框图:

当不同Zone之间需要切换时,运行在机器模式的可信固件需要对Zone的上下文进行保存和切换。虽然看起来这会带来开销,但像ARM TrustZone的虚拟化技术同样需要对世界进行切换,这部分切换的工作同样被隐藏在Monitor的可信固件中。不同的是,ARM TrustZone只虚拟出了2个世界,而玄铁RISC-V处理器在安全扩展之后,最多允许同时支持16个Zone,这为以后的软件安全方案提供了更多的可能。

Mips 软件tee方案

在mips 指令集架构上比较著名的tee方案为SierraTEE

SierraTEE 采用mips的硬件虚拟化辅助来实现teeos 和 richos 之间的运行环境隔离

下图为 SierraTEE 的基础框架:

绿色部分为 SierraTEE 的组件.

  • host os中 user mode下的GPI 库, 即通用的api, app 可以通过该库的api 发送安全请求, 该请求首先会发到 host os的kernel的负责接管安全需求的driver.
  • host os中 os mode下的 kernel 中添加的 Secure Driver, 该Driver 负责向 hypervisor 发送安全请求
  • hypervisor 虚拟化mode下的 hypervisor TEE HAL, 用来接收 rich os 和 teeos的请求
  • secure os, 该os 和 rich os 处于同一mode, 可以把该os理解为guest os. hypervisor monitor 监视层收到 host os的安全请求后, 会进行os切换, 切换到对应的 teeos 处理对应的请求.
  • secure os的GPI库, 即符合某个规范的通用api, 该api 是给secure os下的app 用的, secure os下的app 也叫ta, 每个ta负责单一的业务.
  • secure os下的app, 也叫ta, 负责单一业务, 每个ta 都有一个uuid, host os下的app 要处理某一安全业务时, 需要指定对应的 ta的uuid, 来处理特定的业务.

可以看到 SierraTEE 是利用了mips的硬件虚拟化辅助来实现运行环境隔离的.

通过vcpu的调度由 host os 切换到 guest os. host os为richos, guest os 为 teeos, 两者之间的通信由高一权限级别的hypervisor mode接管.

riscv tee 方案

riscv 开源的tee方案有伯克利的keystone 和 上交大实验室的 蓬莱 方案.

两者的架构差不多

下图是大概的架构

其中绿色部分为 keystone/蓬莱 方案增加的组件.

  • rich os 中 secure clint api 库, 供richos 下的u-mode下的app使用, 用来发送安全请求
  • rich os的kernel 中添加 secure driver, 负责承接 u-mode下的app请求, 并负责解析加载 tee enclave app elf, 发送安全请求给opensbi
  • M-mode的opensbi中新增的 secure hal 负责richos 和 tee运行环境的切换, 还有PMP 的配置, tee 运行环境的内存和rich os的运行内存是隔离的, 由cpu的core的配置的pmp策略进行保护
  • tee 运行环境包含了 S-mode 的rt 运行时和 U-mode的enclave app 两部分构成, 最终由enclave app负责具体的安全边界计算. 而rt 运行时则负责 trap 中断等.

值得注意的是这两个方案目前都比较基础, secure 世界运行的实体还没进化到完全os的阶段, 是由运行时(Supervisor mode) + enclave app (user mode) 组成, 只能处理比较简单的请求, 也没有定义通用的符合tee规范的 GPI. 所以使用场景是受限的. enclave app所能使用的基础的软件接口非常少. 只能写比较简单的类似baremental的应用, 使用受限.

而大型的teeos 如op-tee 还没有对riscv 适配完成.

首先 probe 失败的问题

需要先添加 iommu capable 函数, 表明 IOMMU_CAP_CACHE_COHERENCY 能力为 true, 否则 probe 时会失败.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static struct iommu_ops xt_iommu_ops = {
.capable = xt_iommu_capable,
}
static bool xt_iommu_capable(struct device *dev, enum iommu_cap cap)
{
switch (cap) {
case IOMMU_CAP_CACHE_COHERENCY:
return true;
case IOMMU_CAP_NOEXEC:
return true;
default:
return false;
}
}

打开 option allow_unsafe_interrupts=1
编译为动态模块时, insmod 会读取 /etc/modprobe.d/iommu_unsafe_interrupts.conf 文件, 将 allow_unsafe_interrupts 设置为 1.

1
options vfio_iommu_type1 allow_unsafe_interrupts=1

如果不是动态模块, 包含在 kernel Image 中, 则需要强制打开

1
static bool allow_unsafe_interrupts = 1;

其次 xt_iommu 在 base 版本上使用的一些接口在升级版本上发生了变化:
iommu_device_set_ops iommu_device_set_fwnode bus_set_iommu 不见了, 取而代之的是总结成了一个接口 iommu_device_register, 该接口支持的参数也有变化.
其次某些函数的参数个数意义变化:
xt_iommu_map xt_iommu_unmap , size 变成了 pgsize 和 pgcount.

需要相应的适配修改.

kernel 的相关 config 需要打开:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
CONFIG_IOMMU_IOVA=y
CONFIG_IOMMU_API=y
CONFIG_IOMMU_SUPPORT=y
CONFIG_IOMMU_DMA=y
unset CONFIG_IOMMUFD
CONFIG_VFIO=y
CONFIG_VFIO_CONTAINER=y
CONFIG_VFIO_IOMMU_TYPE1=y
CONFIG_VFIO_VIRQFD=y
CONFIG_VFIO_PCI_CORE=y
CONFIG_VFIO_PCI_MMAP=y
CONFIG_VFIO_PCI_INTX=y
CONFIG_VFIO_PCI=y
CONFIG_PCI=y
CONFIG_PCI_DOMAINS=y
CONFIG_PCI_DOMAINS_GENERIC=y
# kvm-mode 下支持vfio
CONFIG_KVM_VFIO=y

移植 iommu 后, 直通给 guest 的网卡 e1000e 报错, log 如下:
经过分析后, 得出结论, 网卡通过 dma 发包出现异常, 最后由 ndo_tx_timeout 回调了 e1000e 的 e1000_tx_timeout 函数将网卡重启.

由监控的 watchdog

1
2
3
WARN_ONCE(1, "NETDEV WATCHDOG: %s (%s): transmit queue %u timed out %u ms\n",
dev->name, netdev_drivername(dev), i, timedout_ms);
dev->netdev_ops->ndo_tx_timeout(dev, i);

打印出了下面的堆栈:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[   42.096669] NETDEV WATCHDOG: eth0 (e1000e): transmit queue 0 timed out 9960 ms
[ 42.248201] WARNING: CPU: 3 PID: 0 at net/sched/sch_generic.c:525 dev_watchdog+0x224/0x228
[ 42.250411] Modules linked in:
[ 42.250692] CPU: 3 PID: 0 Comm: swapper/3 Not tainted 6.4.0-rc1-00026-g80e62bc8487b-dirty #91
[ 42.250818] Hardware name: riscv-virtio,qemu (DT)
[ 42.250926] epc : dev_watchdog+0x224/0x228
[ 42.251014] ra : dev_watchdog+0x224/0x228
[ 42.251042] epc : ffffffff806e48dc ra : ffffffff806e48dc sp : ff20000000693b90
[ 42.251058] gp : ffffffff814a2b00 tp : ff600000018cd240 t0 : ffffffff8082fba0
[ 42.251074] t1 : 0720072007200720 t2 : 4157205645445445 s0 : ff20000000693c00
[ 42.251089] s1 : ff600000024bc498 a0 : 0000000000000042 a1 : ffffffff8147e108
[ 42.251103] a2 : 00000000ffffefff a3 : fffffffffffffffe a4 : b6182cbfc7d9c400
[ 42.251117] a5 : b6182cbfc7d9c400 a6 : 0000000000000050 a7 : ffffffff8048d172
[ 42.251131] s2 : ff600000024bc000 s3 : ff600000018e3a00 s4 : ff600000024bc3e0
[ 42.251145] s5 : 0000000000000000 s6 : ffffffff8140a980 s7 : 00000000000026e8
[ 42.251159] s8 : ffffffff80c15470 s9 : ffffffff81408088 s10: 0000000000000101
[ 42.251173] s11: 0000000000000282 t3 : ff60000001818f00 t4 : ff60000001818f00
[ 42.251186] t5 : ff60000001818000 t6 : ff20000000693978
[ 42.251199] status: 0000000200000120 badaddr: 0000000000000000 cause: 0000000000000003
[ 42.251303] [<ffffffff806e48dc>] dev_watchdog+0x224/0x228
[ 42.251402] [<ffffffff800940e4>] call_timer_fn.constprop.0+0x14/0x5e
[ 42.252827] [<ffffffff800941b8>] expire_timers+0x8a/0xbc
[ 42.252840] [<ffffffff80094782>] run_timer_softirq+0xe0/0x202
[ 42.252850] [<ffffffff80844460>] __do_softirq+0x100/0x27e
[ 42.256472] [<ffffffff80024a9c>] __irq_exit_rcu+0xa8/0xde
[ 42.258214] [<ffffffff80024bb6>] irq_exit_rcu+0xc/0x14
[ 42.258389] [<ffffffff8083c238>] do_irq+0x6c/0x86
[ 42.258543] [<ffffffff800035ac>] ret_from_exception+0x0/0x64
[ 42.258646] [<ffffffff8083cb2a>] default_idle_call+0x26/0x34
[ 42.259488] [<ffffffff8005a7ee>] do_idle+0x206/0x226
[ 42.259983] [<ffffffff8005a970>] cpu_startup_entry+0x1a/0x1c
[ 42.259997] [<ffffffff8000618a>] handle_IPI+0x0/0xfc
[ 45.988551] e1000e 0000:00:01.0 eth0: Reset adapter unexpectedly
[ 46.743848] e1000e 0000:00:01.0 eth0: NIC Link is Up 1000 Mbps Full Duplex, Flow Control: Rx/Tx

首先状态上来说, 中断是正常的, 但是 dma remapping 不正常.
在 riscv 基础版本上 (未开启 AIA), 只支持 intx 线中断模式
e1000_intr 中断处理函数可以正常触发.

再由不同版本对比后, 发现 qemu (host) / qemu (guest) 以及 kernel (guest) 为不变量, 变量仅有 kernel (host) 从 5.10 版本升级到了 6.4 版本.
在 kernel 变更版本后, 相对应的 vfio 框架发生了一些变化, 在确认 5.10 -> 6.4 iommu / vfio 相关的 config 都相同时, 无明显的其他异常 log.
无明显的排查点, 再往下追只能根据代码行为去正向跟踪.

qemu xmmuv1 模拟行为

正常的 log, 未开启虚拟化时, host 中对 e1000e 网卡的处理就经由了 dma, 而在将 kernel 版本升级到 6.4 后, 却没触发对应的 xmmuv1_translate.
说明 base 版本上开了 e1000e 网卡的 iommu 支持, 而升级版本后关闭了网卡的 iommu 支持.
未开启虚拟化时, 还没有涉及到 vfio 的特性, 所以应重点排查 iommu 相关的 feature.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#0  0x000055555591fa12 in xmmuv1_translate (mr=<optimized out>, addr=4294963200, flag=<optimized out>, iommu_idx=<optimized out>) at ../hw/riscv/xmmuv1.c:186
#1 0x00005555559fe0b6 in address_space_translate_iommu (iommu_mr=0x555557025af0, xlat=xlat@entry=0x7fffe9c61d40, plen_out=plen_out@entry=0x7fffe9c61d38, page_mask_out=page_mask_out@entry=0x0, is_write=is_write@entry=false, is_mmio=true, target_as=0x7fffe9c61cc8, attrs=...) at ../softmmu/physmem.c:435
#2 0x00005555559fe340 in flatview_do_translate (fv=fv@entry=0x7ffee830ab40, addr=addr@entry=4294963200, xlat=xlat@entry=0x7fffe9c61d40, plen_out=plen_out@entry=0x7fffe9c61d38, page_mask_out=page_mask_out@entry=0x0, is_write=false, is_mmio=true, target_as=0x7fffe9c61cc8, attrs=...) at ../softmmu/physmem.c:508
#3 0x00005555559feed1 in flatview_translate (fv=fv@entry=0x7ffee830ab40, addr=addr@entry=4294963200, xlat=xlat@entry=0x7fffe9c61d40, plen=plen@entry=0x7fffe9c61d38, is_write=is_write@entry=false, attrs=...) at ../softmmu/physmem.c:568
#4 0x0000555555a02b91 in flatview_read (fv=0x7ffee830ab40, addr=addr@entry=4294963200, attrs=attrs@entry=..., buf=buf@entry=0x7fffe9c61e10, len=len@entry=16) at ../softmmu/physmem.c:2753
#5 0x0000555555a02cf8 in address_space_read_full (as=0x7fffe8416250, addr=addr@entry=4294963200, attrs=..., buf=buf@entry=0x7fffe9c61e10, len=len@entry=16) at ../softmmu/physmem.c:2770
#6 0x0000555555a02e31 in address_space_rw (as=<optimized out>, addr=addr@entry=4294963200, attrs=..., attrs@entry=..., buf=buf@entry=0x7fffe9c61e10, len=len@entry=16, is_write=is_write@entry=false) at ../softmmu/physmem.c:2798
#7 0x00005555557bd7e9 in dma_memory_rw_relaxed (attrs=..., dir=DMA_DIRECTION_TO_DEVICE, len=16, buf=0x7fffe9c61e10, addr=4294963200, as=<optimized out>) at /home/liguang/program/riscv-lab/qemu/include/sysemu/dma.h:87
#8 0x00005555557bd7e9 in dma_memory_rw (attrs=..., dir=DMA_DIRECTION_TO_DEVICE, len=16, buf=0x7fffe9c61e10, addr=4294963200, as=<optimized out>) at /home/liguang/program/riscv-lab/qemu/include/sysemu/dma.h:130
#9 0x00005555557bd7e9 in pci_dma_rw (attrs=..., dir=DMA_DIRECTION_TO_DEVICE, len=16, buf=0x7fffe9c61e10, addr=<optimized out>, dev=<optimized out>) at /home/liguang/program/riscv-lab/qemu/include/hw/pci/pci_device.h:233
#10 0x00005555557bd7e9 in pci_dma_read (len=16, buf=0x7fffe9c61e10, addr=<optimized out>, dev=<optimized out>) at /home/liguang/program/riscv-lab/qemu/include/hw/pci/pci_device.h:252
#11 0x00005555557bd7e9 in e1000e_start_xmit (core=0x7fffe8418eb0, txr=txr@entry=0x7fffe9c61e80) at ../hw/net/e1000e_core.c:944
#12 0x00005555557bdda6 in e1000e_set_tdt (core=<optimized out>, index=<optimized out>, val=<optimized out>) at ../hw/net/e1000e_core.c:2456
#13 0x00005555557c1130 in e1000e_core_write (core=0x7fffe8418eb0, addr=<optimized out>, val=1, size=4) at ../hw/net/e1000e_core.c:3280
#14 0x00005555557b7b66 in e1000e_mmio_write (opaque=<optimized out>, addr=<optimized out>, val=<optimized out>, size=<optimized out>) at ../hw/net/e1000e.c:112
#15 0x00005555559f9060 in memory_region_write_accessor (mr=0x7fffe8418a60, addr=14360, value=<optimized out>, size=4, shift=<optimized out>, mask=<optimized out>, attrs=...) at ../softmmu/memory.c:493
#16 0x00005555559f8a8b in access_with_adjusted_size (addr=addr@entry=14360, value=value@entry=0x7fffe9c62038, size=size@entry=4, access_size_min=<optimized out>, access_size_max=<optimized out>, access_fn=access_fn@entry=0x5555559f8ffe <memory_region_write_accessor>, mr=0x7fffe8418a60, attrs=...) at ../softmmu/memory.c:569
#17 0x00005555559f8d58 in memory_region_dispatch_write (mr=mr@entry=0x7fffe8418a60, addr=addr@entry=14360, data=<optimized out>, data@entry=1, op=op@entry=MO_32, attrs=...) at ../softmmu/memory.c:1533
#18 0x0000555555a3d245 in io_writex (env=env@entry=0x55555669e430, full=0x7ffef0d6cf38, mmu_idx=1, val=val@entry=1, addr=18446743867826518040, retaddr=retaddr@entry=140736773646269, op=MO_32) at ../accel/tcg/cputlb.c:1435
#19 0x0000555555a40cf1 in do_st_4 (ra=140736773646269, memop=<optimized out>, mmu_idx=<optimized out>, val=1, p=0x7fffe9c62140, env=0x55555669e430) at ../accel/tcg/cputlb.c:2772
#20 0x0000555555a40cf1 in do_st4_mmu (env=0x55555669e430, addr=<optimized out>, val=1, oi=<optimized out>, ra=140736773646269) at ../accel/tcg/cputlb.c:2850
#21 0x0000555555a42727 in helper_stl_mmu (env=<optimized out>, addr=<optimized out>, val=<optimized out>, oi=<optimized out>, retaddr=<optimized out>) at ../accel/tcg/cputlb.c:2866
  1. 怀疑点 CONFIG_IOMMU_DMA, 这个开关在 base 和升级版本上都开了.

升级版本的开机日志:
dmesg | grep -E “DMAR|IOMMU”

1
2
[    1.156855] Failed to set up IOMMU for device Fixed MDIO bus.0; retaining platform DMA ops
[ 1.102567] Failed to set up IOMMU for device riscv-pmu; retaining platform DMA ops

base 版本中的开机日志中并没有上述异常.

先看下这处异常:

1
2
3
4
5
6
7
8
9
struct iommu_domain *iommu_get_domain_for_dev(struct device *dev)
{
struct iommu_domain *domain;
struct iommu_group *group;
group = iommu_group_get(dev);
// error: 此处domain 为 null, 未找到 dev->group->domain
domain = group->domain;
return domain;
}

该 domain 应该来自于 iommu driver 设置的 default_domain
跟踪堆栈, 发现 xt_iommu driver 分配 default_domain 时失败了.

1
2
3
4
5
6
7
#0  xt_iommu_domain_alloc (type=<optimized out>) at ../drivers/iommu/xuantie-iommu.c:365
#1 0xffffffff804f7444 in __iommu_domain_alloc (bus=0xffffffff8150c0a0 <platform_bus_type>, type=<optimized out>) at ../drivers/iommu/iommu.c:1987
#2 0xffffffff804f754c in iommu_group_alloc_default_domain (bus=bus@entry=0xffffffff8150c0a0 <platform_bus_type>, group=group@entry=0xff60000080015e00, type=<optimized out>) at ../drivers/iommu/iommu.c:1667
#3 0xffffffff804f7d46 in probe_alloc_default_domain (group=0xff60000080015e00, bus=0xffffffff8150c0a0 <platform_bus_type>) at ../drivers/iommu/iommu.c:1819
#4 bus_iommu_probe (bus=bus@entry=0xffffffff8150c0a0 <platform_bus_type>) at ../drivers/iommu/iommu.c:1882
#5 0xffffffff804f7e24 in iommu_device_register (iommu=0xff600000801f3d20, ops=ops@entry=0xffffffff8150b120 <xt_iommu_ops>, hwdev=hwdev@entry=0xff600000802c1010) at ../drivers/iommu/iommu.c:245
#6 0xffffffff80837748 in xt_iommu_device_probe (pdev=0xff600000802c1000) at ../drivers/iommu/xuantie-iommu.c:718

__iommu_domain_alloc 函数中, 这个地方退出了, 而 base 版本没有这个逻辑.

1
2
3
4
if (iommu_is_dma_domain(domain) && iommu_get_dma_cookie(domain)) {
iommu_domain_free(domain);
domain = NULL;
}

将这段注释掉后, 继而需要将 pci_bus_type 注册 iommu_ops, 但其他的 bus 类型 (platform_bus_type) 不能注册, 会导致异常, base 版本上的 xtiommu 只支持 pci_bus.
但升级版本已经没有 bus_set_iommu 相关的函数了, 只能改代码进行定制.

在改完后, 网卡在进行 dma 操作时, 已经使用 xtiommu, 但仍出现了异常.
还是表现在地址翻译时, 触发的地址 iova 不正常.

正常的 log:
xmmuv1_translate addr 4294963200
xmmuv1_translate addr 4294963201
xmmuv1_translate addr 4294963202
xmmuv1_translate addr 4294963203

而异常的 log:
xmmuv1_translate addr 4294963200
xmmuv1_translate addr 4294963204
xmmuv1_translate addr 4294963208
xmmuv1_translate addr 4294963212
退出

堆栈:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#0  0x000055555591f75b in xmmuv1_translate (mr=0x555557025af0, addr=4294963201, flag=IOMMU_RO, iommu_idx=0) at ../hw/riscv/xmmuv1.c:104
#1 0x00005555559fe09a in address_space_translate_iommu (iommu_mr=0x555557025af0, xlat=xlat@entry=0x7ffef59fdcc8, plen_out=plen_out@entry=0x7ffef59fdd20, page_mask_out=page_mask_out@entry=0x0, is_write=is_write@entry=false, is_mmio=true, target_as=0x7ffef59fdc38, attrs=...) at ../softmmu/physmem.c:435
#2 0x00005555559fe324 in flatview_do_translate (fv=fv@entry=0x7ffef0252180, addr=addr@entry=4294963201, xlat=xlat@entry=0x7ffef59fdcc8, plen_out=plen_out@entry=0x7ffef59fdd20, page_mask_out=page_mask_out@entry=0x0, is_write=false, is_mmio=true, target_as=0x7ffef59fdc38, attrs=...) at ../softmmu/physmem.c:508
#3 0x00005555559feeb5 in flatview_translate (fv=fv@entry=0x7ffef0252180, addr=addr@entry=4294963201, xlat=xlat@entry=0x7ffef59fdcc8, plen=plen@entry=0x7ffef59fdd20, is_write=is_write@entry=false, attrs=..., attrs@entry=...) at ../softmmu/physmem.c:568
#4 0x0000555555a02aa9 in flatview_read_continue (fv=fv@entry=0x7ffef0252180, addr=4294963201, addr@entry=4294963200, attrs=..., ptr=ptr@entry=0x7ffef59fde10, len=15, len@entry=16, addr1=<optimized out>, l=<optimized out>, mr=0x55555656aee0) at ../softmmu/physmem.c:2738
#5 0x0000555555a02baf in flatview_read (fv=0x7ffef0252180, addr=addr@entry=4294963200, attrs=attrs@entry=..., buf=buf@entry=0x7ffef59fde10, len=len@entry=16) at ../softmmu/physmem.c:2757
#6 0x0000555555a02cdc in address_space_read_full (as=0x7fffe8416250, addr=addr@entry=4294963200, attrs=..., buf=buf@entry=0x7ffef59fde10, len=len@entry=16) at ../softmmu/physmem.c:2770
#7 0x0000555555a02e15 in address_space_rw (as=<optimized out>, addr=addr@entry=4294963200, attrs=..., attrs@entry=..., buf=buf@entry=0x7ffef59fde10, len=len@entry=16, is_write=is_write@entry=false) at ../softmmu/physmem.c:2798
#8 0x00005555557bd7e9 in dma_memory_rw_relaxed (attrs=..., dir=DMA_DIRECTION_TO_DEVICE, len=16, buf=0x7ffef59fde10, addr=4294963200, as=<optimized out>) at /home/liguang/program/riscv-lab/qemu/include/sysemu/dma.h:87
#9 0x00005555557bd7e9 in dma_memory_rw (attrs=..., dir=DMA_DIRECTION_TO_DEVICE, len=16, buf=0x7ffef59fde10, addr=4294963200, as=<optimized out>) at /home/liguang/program/riscv-lab/qemu/include/sysemu/dma.h:130
#10 0x00005555557bd7e9 in pci_dma_rw (attrs=..., dir=DMA_DIRECTION_TO_DEVICE, len=16, buf=0x7ffef59fde10, addr=<optimized out>, dev=<optimized out>) at /home/liguang/program/riscv-lab/qemu/include/hw/pci/pci_device.h:233
#11 0x00005555557bd7e9 in pci_dma_read (len=16, buf=0x7ffef59fde10, addr=<optimized out>, dev=<optimized out>) at /home/liguang/program/riscv-lab/qemu/include/hw/pci/pci_device.h:252
#12 0x00005555557bd7e9 in e1000e_start_xmit (core=0x7fffe8418eb0, txr=txr@entry=0x7ffef59fde80) at ../hw/net/e1000e_core.c:944
#13 0x00005555557bdda6 in e1000e_set_tdt (core=<optimized out>, index=<optimized out>, val=<optimized out>) at ../hw/net/e1000e_core.c:2456
#14 0x00005555557c1130 in e1000e_core_write (core=0x7fffe8418eb0, addr=<optimized out>, val=1, size=4) at ../hw/net/e1000e_core.c:3280
#15 0x00005555557b7b66 in e1000e_mmio_write (opaque=<optimized out>, addr=<optimized out>, val=<optimized out>, size=<optimized out>) at ../hw/net/e1000e.c:112

在 flatview_read_continue 处追踪, 发现步长来自于翻译结果, 进一步跟踪发现
xt iommu 在第一级地址转换处出问题了 riscv_one_stage, 地址翻译出错了.

qemu 中的 xt iommu 是未改动的, 且在 base 版本和升级版本都是同一份, 所以问题应该出现在 map 的地方.

跟踪 kernel 中 xt_iommu_map 的过程

1
2
3
4
5
6
7
8
9
10
11
12
#0  xt_iommu_map (domain=0xff6000008023a6b0, iova=4294963200, paddr=4397146112, pgsize=4096, pgcount=1, iommu_prot=7, gfp=3520, mapped=0xff20000000d13218) at ../drivers/iommu/xuantie-iommu.c:70
#1 0xffffffff804f56bc in __iommu_map_pages (mapped=0xff20000000d13218, gfp=3520, prot=7, size=4096, paddr=<optimized out>, iova=4294963200, domain=0xff6000008023a6b0) at ../drivers/iommu/iommu.c:2359
#2 __iommu_map (domain=domain@entry=0xff6000008023a6b0, iova=iova@entry=4294963200, paddr=<optimized out>, paddr@entry=4397146112, size=size@entry=4096, prot=prot@entry=7, gfp=gfp@entry=3520) at ../drivers/iommu/iommu.c:2405
#3 0xffffffff804f58f4 in iommu_map_sg (domain=domain@entry=0xff6000008023a6b0, iova=iova@entry=4294963200, sg=sg@entry=0xff600000822a9ac0, nents=<optimized out>, prot=prot@entry=7, gfp=gfp@entry=3520) at ../drivers/iommu/iommu.c:2560
#4 0xffffffff804f9644 in __iommu_dma_alloc_noncontiguous (dev=dev@entry=0xff600000802890c8, size=size@entry=4096, sgt=sgt@entry=0xff20000000d133f8, gfp=3520, attrs=attrs@entry=0, prot=...) at ../drivers/iommu/dma-iommu.c:846
#5 0xffffffff804fa042 in iommu_dma_alloc_remap (attrs=0, prot=..., gfp=<optimized out>, dma_handle=0xff600000808ce990, size=4096, dev=0xff600000802890c8) at ../drivers/iommu/dma-iommu.c:872
#6 iommu_dma_alloc (dev=0xff600000802890c8, size=4096, handle=0xff600000808ce990, gfp=<optimized out>, attrs=0) at ../drivers/iommu/dma-iommu.c:1462
#7 0xffffffff80088072 in dma_alloc_attrs (dev=0xff600000802890c8, size=4096, dma_handle=dma_handle@entry=0xff600000808ce990, flag=flag@entry=3264, attrs=attrs@entry=0) at ../kernel/dma/mapping.c:522
#8 0xffffffff805aea92 in dma_alloc_coherent (gfp=3264, dma_handle=0xff600000808ce990, size=<optimized out>, dev=<optimized out>) at ../include/linux/dma-mapping.h:423
#9 e1000_alloc_ring_dma (adapter=0xff600000811a4980, adapter=0xff600000811a4980, ring=0xff600000808ce980) at ../drivers/net/ethernet/intel/e1000e/netdev.c:2317
#10 e1000e_setup_tx_resources (tx_ring=0xff600000808ce980) at ../drivers/net/ethernet/intel/e1000e/netdev.c:2345
#11 0xffffffff805b044a in e1000e_open (netdev=0xff600000811a4000) at ../drivers/net/ethernet/intel/e1000e/netdev.c:4630

追踪 map 的过程, 发现 xt iommu 驱动代码中做 mmu 映射时, 启用的 sv39 mode, 第一级的 pgd_shift 错误了使用了系统中定义的
PGDIR_SHIFT

这个值是跟着系统的页表模式走的, 当前系统使用了 sv57 mode 5 级页表, 所以这个值是 48, 而 sv39 mode 这个值应该是 30.

1
2
3
4
for (i = 0; i < loop; ++i) {
vpn2_idx = (iova >> PGDIR_SHIFT) & PAGE_TABLE_LEVEL_MASK;
vpn1_idx = (iova >> PMD_SHIFT) & PAGE_TABLE_LEVEL_MASK;
vpn0_idx = (iova >> PAGE_SHIFT) & PAGE_TABLE_LEVEL_MASK;

将 PGDIR_SHIFT 修改为 PGDIR39_SHIFT (30) 后
再进行测试, 发现 host 中启用 xt iommu 进行 dma 的寻址终于正常了.

host 中正常后, 再进行 guest 直通网卡测试
发现 guest kvm-mode 下直通的 e1000e 网卡也正常工作了

sm_init 调用栈

1
2
3
4
5
6
7
8
#0  sm_init (cold_boot=cold_boot@entry=1) at /home/liguang/program/riscv-lab/nuclei-linux-sdk/opensbi/lib/sbi/optee/sm.c:120
#1 0x00000000a00026aa in nuclei_final_init (cold_boot=<optimized out>)
at /home/liguang/program/riscv-lab/nuclei-linux-sdk/opensbi/platform/nuclei/evalsoc/platform.c:66
#2 0x00000000a0000c18 in sbi_platform_final_init (cold_boot=1, plat=0xa00138a8 <platform>)
at /home/liguang/program/riscv-lab/nuclei-linux-sdk/opensbi/include/sbi/sbi_platform.h:401
#3 init_coldboot (hartid=0, scratch=0xa002d000) at /home/liguang/program/riscv-lab/nuclei-linux-sdk/opensbi/lib/sbi/sbi_init.c:295
#4 sbi_init (scratch=0xa002d000) at /home/liguang/program/riscv-lab/nuclei-linux-sdk/opensbi/lib/sbi/sbi_init.c:425
#5 0x00000000a00004b6 in _start_warm () at /home/liguang/program/riscv-lab/nuclei-linux-sdk/opensbi/firmware/fw_base.S:443

重点关注 sm_init 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
-+ void sm_init(bool cold_boot)
\ -|+ if cold_boot
\ -+ sbi_ecall_register_extension(&ecall_optee);
\ - .extid_start = SBI_EXT_OPTEE "0x4F505445"
| - .handle = sbi_ecall_optee_handler, "注册 optee ext handler"
| - sm_region_id = smm_init(); "添加 opensbi text 段 pmp entry, 并保存 id为 sm_region_id"
| - os_region_id = osm_init(); "添加 0xfffffffff bottom entry"
| - tee_region_id = teem_init(); "添加 FW_OPTEE_TZDRAM_BASE optee_os 的text 内存 pmp entry"
| - shm_region_id = shm_init(); "添加 share memory (secure与non sec 的共享内存的) pmp entry"
| - plicm_region_id = plicm_init(); "添加 plic 的 mmio的 pmp entry"
| - timer_region_id = timerm_init(); "添加 timer 的 mmio的pmp entry"
"没有判断 cold_boot 说明是所有核都会走"
| -+ pmp_init() "将所有可用的 pmpaddr pmpcfg 置0"
| -+ pmp_set_keystone(sm_region_id, PMP_NO_PERM); "设置 opensbi text 段不可读,这个是对 S-mode 有效,对 M-mode 不起作用"
| -+ pmp_set_keystone(os_region_id, PMP_ALL_PERM); "设置优先级最低的 osm entry 0xfffffffff 保底读写执行权限,排在它前面的 entry 如果设置了不一样的权限, 以前面的 entry 设置的为准"
| -+ pmp_set_keystone(tee_region_id, PMP_NO_PERM); "设置 optee text 无权限"
| -|+ if cold_boot
\ -+ opteed_init()
\ - sbi_memset(psci_ns_context, 0, sizeof(psci_ns_context)); "normal world context 初始化为 0"
| - sbi_memset(opteed_sp_context, 0, sizeof(opteed_sp_context)); "secure world context 初始化为 0"
| -+ img_entry_point.sec_attr = SECURE; "保存状态, 后面会用. 该结构体最终会被放到 当前 cpu context 中"
| -+ img_entry_point.pc = OPTEE_OS_LOAD_ADDR; "optee 的 text 段起始地址"
| - img_entry_point.arg0 = current_hartid(); "cpu core id"
| - img_entry_point.arg1 = 8*1024*1024;
| - img_entry_point.arg2 = FW_JUMP_FDT_ADDR; "fdt 的地址,optee 和 opensbi 共用一个 fdt"
| -+ cm_init_my_context(&img_entry_point);
\ -+ cm_init_context_by_index(current_hartid(), &img_entry_point);
\ -+ ctx = cm_get_context_by_index(cpu_idx, &img_entry_point->sec_attr); "根据 cpu id 和 sec_attr 属性找到 context 指针"
\ - if secure? &opteed_sp_context[cpu_idx].cpu_ctx : &psci_ns_context[cpu_idx];
"secure 和 non sec 分别有一个context 数组, 按cpuid 保存对应的 当前cpu的context"
| -+ cm_setup_context(ctx, ep); "填 context "
\ - ctx->sec_attr = (ep->sec_attr == SECURE) ? SECURE : NON_SECURE;
| - ctx->gp_regs.mepc = ep->pc;
| - ctx->gp_regs.mstatus = (1 << MSTATUS_MPP_SHIFT); "S-mode"
| - ctx->gp_regs.a0 = ep->arg0;
| - ctx->gp_regs.a1 = ep->arg1;
| - ctx->gp_regs.a2 = ep->arg2;
| - ctx->gp_regs.... 直到 arg7
| -|+ if ep->sec_attr == SECURE
\ - ctx->s_csrs.sie = 0; "初始 sie 为0, S-mode 中断关闭"
| - saved_mie = csr_read(CSR_MIE); "保存中断使能状态"
| - csr_write(CSR_MIE, 0); "关闭所有中断"
| - rc = opteed_synchronous_sp_entry(optee_ctx); "进入 sec world optee_os 的准备工作, 及要进入 optee_os, 下面拆解"
-----------------------------------------------------------------------------------------------------
"回到 M-mode的 opensbi"
-----------------------------------------------------------------------------------------------------
| -+ ... sbi_ecall_optee_handler 处理流程 -->1
| - csr_write(CSR_MIE, saved_mie); "恢复中断状态"
| - sbi_memset(&img_entry_point, 0, sizeof(entry_point_info_t));
| - img_entry_point.sec_attr = NON_SECURE; "下一个 image 为 non sec 的 u-boot"
| - cm_init_my_context(&img_entry_point); "初始化 non sec 的 context"
| - cm_set_next_eret_context(NON_SECURE);

下面对 opteed_synchronous_sp_entry 进行拆解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
-+ uint64_t opteed_synchronous_sp_entry(optee_context_t *optee_ctx)
\ -+ cm_sysregs_context_restore(SECURE); "从context 中恢复 s 开头的 csr, 这里主要是 sepc sie "
| -+ cm_set_next_eret_context(SECURE);
\ -+ ctx = cm_get_context(security_state); "拿 当前 cpu的 context 指针"
| -+ cm_set_next_context(ctx);
\ - next_cpu_context_ptr[current_hartid()] = context; "设置 next_cpu_context_ptr 全局变量, 保存当前cpu的下一个 context"
| -|+ if secure_state == SECURE ?
\ -+ switch_plic_int_enable_mode(SECURE); "设置中断使能状态为 secure_state 方案"
| -+ switch_vector_sec();
\ - csr_write(CSR_MTVEC, &_trap_handler_sec); "设置 secure 方案的 trap handler"
| - osm_pmp_set(PMP_NO_PERM); "bottom pmp entry 设置所有内存无权限"
| - shm_pmp_set(PMP_ALL_PERM); "sec 与 non sec 的 share memory 设置 all 权限"
| - plicm_pmp_set(PMP_ALL_PERM);
| - timerm_pmp_set(PMP_ALL_PERM);
| - teem_pmp_set(PMP_ALL_PERM); "设置 tee 的 text data bss 等的 all 权限"
| -+ rc = opteed_enter_sp(&optee_ctx->c_rt_ctx); "进入汇编函数, 保存当前 opensbi的 cpu context,
恢复 context 中保存的上下文, 即为 optee_os 准备的初始的 cpu context"
\ - REG_L s1, SBI_TRAP_REGS_OFFSET(mepc)(s0)
| - csrw CSR_MEPC, s1
| - REG_L s1, SBI_TRAP_REGS_OFFSET(mstatus)(s0)
| - csrw CSR_MSTATUS, s1
| - mret "进入到 optee_os, M-mode 切换为 S-mode"

–>1 optee_os 初始化完毕后, 会发 ecall 调用, 调用号 SBI_EXT_OPTEE “0x4F505445” 回到 opensbi 中, opensbi 由前面设置的 _trap_handler_sec 处理, 进而由
opensbi sbi_ecall_optee_handler 处理该 ecall 调用,其中 funcid 为 TEESMC_OPTEED_RETURN_ENTRY_DONE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-+ static int sbi_ecall_optee_handler(unsigned long extid, unsigned long funcid,
const struct sbi_trap_regs *regs,
unsigned long *out_val,
struct sbi_trap_info *out_trap)
\ -+ switch (funcid)
\ -+ case TEESMC_OPTEED_RETURN_ENTRY_DONE
\ -+ cm_gpregs_context_save(SECURE, regs); "保存 optee_os 的上下文到 context 中"
\ - ctx = cm_get_context(security_state);
| - sbi_memcpy(&ctx->gp_regs, trap_reg, sizeof(struct sbi_trap_regs)); "regs 已经由 _trap_handler_sec 保存下来了, 这里转移到 context 下"
| -+ cm_sysregs_context_save(SECURE); "保存 s 开头的 csr 到 context 下, 这里不用参数, 因为 M-mode 基本上不会改动 S-mode 的寄存器, 直接读即可"
| - optee_vector_table = (optee_vectors_t *) regs->a1; "regs->a1锚点 optee_os的 thread_vector_table 基址在 ecall 时会保存到 a1 寄存器中, 进而转移到这里"
| - if optee_vector_table ? set_optee_pstate(optee_ctx->state, OPTEE_PSTATE_ON) "optee_os的 power state, 这里on 表示正在运行"
| -+ opteed_synchronous_sp_exit(optee_ctx, regs->a1); "回到 进入 optee_os 时的地方"
\ -+ cm_sysregs_context_save(SECURE);
\ -+ opteed_exit_sp(optee_ctx->c_rt_ctx, ret);
\ - REG_L s0, (12-13) * __SIZEOF_POINTER__(sp)
| - ... "恢复 callee 即 s0-s11 寄存器"
| - REG_L ra, (0-13) * __SIZEOF_POINTER__(sp) "恢复 ra"
| - ret "回到 进入 optee_os 时的地方 接着往下执行"

optee_os 启动流程

上面讲到在调用 opteed_synchronous_sp_entry->opteed_enter_sp mret 后进入了 optee_os 中
下面就讲下 optee_os 的启动流程.

optee_os/core/arch/riscv/kernel/entry.S

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
-+ _start
\ - mv s1, a0 "保存hartid"
| - la a0, reset_vect_table
| - csrw stvec, a0 "保存 stvec 中断入口 为 reset_vect_table"
| -+ clear_bss __bss_start, __bss_end
| -+ set_sp s1 "为 hartid 设置 sp"
| -+ jal thread_init_thread_core_local "初始化线程本地存储"
| -+ jal dcache_cleaninv_range __text_start, cached_mem_end "dcache inv clean"
| -+ jal console_init "初始化console"
| -+ jal core_init_mmu_map(0, boot_mmu_config) "初始化mmu 映射mmu 页表"
| -+ jal enable_mmu(hartid) "开启mmu"
| -+ jal boot_init_primary_early "主核初始化"
\ -+ init_primary()
\ -+ thread_init_core_local_stacks() "由于std smc需要支持线程(thread)功能,而每个线程都需要含有独立的线程栈,
用于保存该线程相关的上下文。该函数用于为每个cpu核初始化相关的线程栈, tmp_stack_va_end用于指定每个ARM核的栈空间"
| -+ thread_set_exceptions(THREAD_EXCP_ALL); "由于在接下来的初始化流程中需要切换异常向量表的基地址,
因此在切换之前需要先mask掉相关的异常使能位"
| - primary_save_cntfrq() "未实现"
| - init_vfp_sec() "未实现,初始化浮点运算单元vfp"
| - thread_get_core_local()->curr_thread = 0; "curr_thread用于表示当前核使用的是哪个线程空间。"
| -+ init_runtime(); "初始化tee运行时所需的各种内存"
| -+ thread_init_boot_thread(); "将当前执行上下文转换为启动线程,
它主要包括从当前cpu的线程栈内存中分配一个栈空间,初始化线程页表和线程状态等"
| -+ thread_init_primary(); "该函数主要用于增强线程的运行安全,如设置线程栈的canary值,以用于监测栈溢出"
| -+ thread_init_per_cpu();
\ -+ thread_init_tvec(); "为每个cpu 设置中断入口为 thread_trap_vect"
| -+ init_sec_mon(nsec_entry); "未实现"
| -+ jal boot_init_primary_late(fdt) "该函数用于建立optee的系统运行环境,
包括从devcicetree中解析相关的配置,初始化中断控制器,调用initcall回调函数等"
\ -+ init_external_dt(fdt); "初始化devicetree,包括映射其物理内存,初始化dtb overlay等"
| -+ tpm_map_log_area(get_external_dt()); "初始化tpm的log buffer,其主要操作是为log buffer建立虚拟地址页表"
| -+ discover_nsec_memory(); "optee与non secure world可通过共享内存通信,它一共支持静态映射和动态映射两种共享内存方式。
其中静态映射shm在optee启动时就为其建立了相应的内存页表,
而动态映射方式可以在运行过程中动态地为shm建立页表。该接口用于从devicetree中获取non secure内存,以支持动态共享内存机制"
| -+ update_external_dt(); "在devicetree中添加与optee相关的一些节点和属性,这些节点和属性将由hlos内核解析并用于初始化相应的模块。
如它会创建/firmware/optee节点,并在该节点中配置一些与optee相关的属性"
| -+ configure_console_from_dt() "从devicetree中解析串口参数,并将optee的控制台切换为该串口"
| - main_init_gic(); "未实现"
| - init_vfp_nsec(); "未实现"
| -+ init_tee_runtime();
\ -+ core_mmu_init_ta_ram(); "用户空间中使用的栈空间是从 tee_mm_sec_ddr 内存池中分配出来的,
该内存池属于MEM_AREA_TA_RAM内存区域,该区域是由OPTEE分配,用于运行TA镜像。"
\ -+ tee_mm_init(&tee_mm_sec_ddr, ps, size, CORE_MMU_USER_CODE_SHIFT, TEE_MM_POOL_NO_FLAGS);
| -+ call_initcalls(); -->2 "调用已注册的initcall回调函数, 被各模块用于注册启动时调用的初始化接口, core/include/initcall.h"
| -+ jal thread_clr_boot_thread "thread 结束阶段, 将thread->state 置为 THREAD_STATE_FREE"
| -+ ecall SBI_EXT_OPTEE a6->TEESMC_OPTEED_RETURN_ENTRY_DONE a1-> thread_vector_table load offset -->3
"ecall 回opensbi, 将optee_os 的 vector address load offset(此处就是其物理地址,这里mmu 是直接映射的) 放到 a1 中, 给opensbi 用,
参照 sbi_ecall_optee_handler 的 regs->a1锚点 "

–>2 initcall 按顺序执行

1
2
3
4
5
6
7
8
9
10
11
12
13
#define preinit_early(fn)		__define_initcall(preinit, 1, fn)
#define preinit(fn) __define_initcall(preinit, 2, fn)
#define preinit_late(fn) __define_initcall(preinit, 3, fn)

#define early_init(fn) __define_initcall(init, 1, fn)
#define early_init_late(fn) __define_initcall(init, 2, fn)
#define service_init(fn) __define_initcall(init, 3, fn)
#define service_init_late(fn) __define_initcall(init, 4, fn)
#define driver_init(fn) __define_initcall(init, 5, fn)
#define driver_init_late(fn) __define_initcall(init, 6, fn)
#define release_init_resource(fn) __define_initcall(init, 7, fn)

#define boot_final(fn) __define_initcall(final, 1, fn)

–>3 thread_vector_table

1
2
3
4
5
6
7
8
9
10
11
12
13
14
FUNC thread_vector_table , : , .identity_map, , nobti
.option push
.option norvc
j vector_std_smc_entry
j vector_fast_smc_entry
j vector_cpu_on_entry
j vector_cpu_off_entry
j vector_cpu_resume_entry
j vector_cpu_suspend_entry
j vector_fiq_entry
j vector_system_off_entry
j vector_system_reset_entry
.option pop
END_FUNC thread_vector_table

中断处理

原生的 riscv 下并没有安全世界/非安全世界的区分, 没有对中断进行区分.
而 arm trustzone 下可以根据 SCR.NS bit 位来标记进入安全世界还是非安全世界或者在进 monitor 时, 是来自于安全世界还是非安全世界.
且 arm 在硬件上做到了将非安全中断绑定到 irq 事件, 安全中断绑定到了 fiq 事件. irq 由 REE 处理, fiq 由 TEE 处理.

因此 riscv 想做 tee 方案,

  • 需要由软件管理安全世界/非安全世界的运行状态
  • 需要有软件管理非安全中断和安全中断
    上述这两点增加了软件实现的复杂度

看下 nuclei 上怎么做到上述这两点的

安全状态标记

opensbi 中实现 ATF 类似的功能, 不同的是, 需要由软件维护安全状态.

  • opteeos 初始化阶段, opensbi 在平台初始化节点, 通过 mret 进入 optee_os, optee_os 完成初始化后返回 opensbi, opensbi 需要保存 optee 的 context, 即安全世界上下文
  • ree os 运行阶段, 当需要 optee 服务时, ree os 通过 ecall 发送 optee 请求, opensbi 收到请求, 保存当前 cpu 状态到非安全 context, 然后恢复安全世界上下文, 将请求给 optee_os 处理. optee_os 处理完后, 发送 ecall 到 opensbi, opensbi 保存安全世界上下文恢复非安全世界上下文, 保存 optee 返回值, 恢复到 ree os 执行.
    维护安全状态中引入了多个数组指针, 保存当前 cpu 安全世界上下文/非安全世界上下文的指针. 以及 next context

从 next context 的 sec_attr 判断是来自 安全世界还是非安全世界

1
2
3
4
5
6
static int is_caller_non_secure(void)
{
cpu_context_t *ctx;
ctx = cm_get_next_context();
return (ctx->sec_attr == NON_SECURE);
}

安全世界状态切换时, 需要更新维护 next context. 这点是比较容易理解的, 无论当前 cpu 是处在安全世界(TEE)还是非安全世界(TEE) 中, 进入这个模式前, 都需要 opensbi 切换世界状态的代码参与, 而这个代码 cm_set_next_context 维护了 sec_attr 的状态.

而 arm 类似判断来自于安全世界/非安全世界的 smc 调用时, 用的 SCR.NS 硬件状态.

维护中断状态

要实现类似 arm FIQ/IRQ 的能力, nuclei 做了如下方案

  • 软件维护了一个表记录哪些中断是安全中断 (arm 使用 group 标记)
  • 安全中断应该由 M-mode 响应还是由 S-mode 响应
    • 进入 TEE, 设置安全中断由 S-mode 响应, 非安全中断由 M-mode 响应.
    • 进入 REE, 设置安全中断由 M-mode 响应, 非安全中断由 S-mode 响应.

当处在 M-mode 时, 中断都由 M-mode 响应, 根据中断类型再分发给 TEE/REE

plic_secure_int 数组中每个元素是由 hartid 和中断号组成,各占16bit, plic_secure_int[0]是个特殊值记录安全中断总个数。

  • plic_secure_int[0]:表示有 plic_secure_int[0] & 0xFFFF 个安全中断
  • plic_secure_int[x] 表示 plic_secure_int[x] & 0xFFFF 这个安全中断绑定到了hartid为plic_secure_int[x] >> 16 的core上
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
// 注册安全中断
int plic_register_sec_interrupt(unsigned int intr) {
hartid = current_hartid();
// 检查安全中断是否已经被注册. 已经注册则返回
for (i = 0 ; i < cnt ; i++)
if ((plic_secure_int[i + 1] & 0xFFFF) == intr)
return true;
// 注册安全中断
plic_secure_int[cnt + 1] = (hartid << 16) | intr;
// 更新安全中断总个数
plic_secure_int[0] = (0xFFFF << 16) | (cnt + 1);

}

int plic_is_sec_interrupt(int intr)
{
int i;
// plic_secure_int[0]是个特殊值记录安全中断总个数。
for(i = 0; i < (plic_secure_int[0] & 0xFFFF); i++)
if (intr == (plic_secure_int[i+1] & 0xFFFF))
return true;
return false;
}


void switch_plic_int_enable_mode(int next_state)
{
int en_mode, plic_int_num;
int i, j;
unsigned int* secint;

plic_int_num = irqchip_plic_get_interrupt_num();
secint = plic_get_sec_interrupt_tab();
for(i = 1; i <= plic_int_num; i++) {
en_mode = irqchip_plic_get_en_mode(i, NON_SECURE);
if (en_mode == -1 || plic_is_sec_interrupt(i))
/* skip disabled interrupt and secure interrupt */
continue;
else {
/*
* set all non-secure interrupt enable to S mode when next_state is NON_SECURE
* set all non-secure interrupt enable to M mode when next_state is SECURE
*/
irqchip_plic_set_en_mode((u32*)&i, next_state == NON_SECURE, NON_SECURE);
}
}
/*
* set all secure interrupt enable to M mode when next_state is NON_SECURE
* set all secure interrupt enable to S mode when next_state is SECURE
*/
for(j = 0; j < (secint[0] & 0xFFFF); j++)
irqchip_plic_set_en_mode(&secint[j+1], !(next_state == NON_SECURE), SECURE);
}

int irqchip_plic_set_en_mode(unsigned int *pintid, unsigned int mode, int secure)
{
hartid = current_hartid();
// 判断安全中断是否绑定到了一个hartid 上, 如果没有则重新绑定到当前hartid上
if (secure == 0) {
/* judge if int_id is bind to a hart */
if ((*pintid >> 16) != 0xFFFF) {
/*
* int_id have been bind to a hart, bind hart
* is not equal current return do nothing
*/
if ((*pintid >> 16) != hartid)
return SBI_EFAIL;
} else {
/* bind int_id to current hart id */
*pintid = (hartid << 16) | (*pintid & 0xFFFF);
}
// 安全中断, int_id 取 低16位
int_id = *pintid & 0xFFFF;
}
else
// 非安全中断, int_id = *pintid
int_id = *pintid;
// 取plic 结构体
plic = plic_hartid2data[hartid];
// 取 intid 对应的 plic寄存器里的 stride offset
int_src_idx = int_id / 32;
...
// 设置 plic 寄存器的 对应 int_id的 ie 使能位, 使能位分为 S-mode 和 M-mode的, 分别设置
// plic_hartid2context[hartid][!!mode] 中保存了对应hartid的 world_index, world_index在 plic里实际为 M-mode 还是 S-mode
// mode为0 plic_hartid2context[hartid][0] 存的是 M-mode 的 offset, plic_hartid2context[hartid][!0=1] 存的是 S-mode 的 offset
// mode: next_state == NON_SECURE next_state为sec, 则 mode 为 0, M-mode offset 置1, S-mode offset 置0
// mode: !(next_state == NON_SECURE) next_state为sec, 则 mode 为 1, S-mode offset 置1, M-mode offset 置0
// mode: next_state == NON_SECURE next_state为non_sec, 则 mode 为 1, S-mode offset 置1, M-mode offset 置0
// mode: !(next_state == NON_SECURE) next_state为non_sec, 则 mode 为 0, M-mode offset 置1, S-mode offset 置0
// 即要进入sec 时, 将非安全中断 M-mode 使能, S-mode 关闭, 将安全中断 M-mode 关闭, S-mode 使能
// 即要进入non_sec 时, 将非安全中断 S-mode 使能, M-mode 关闭, 将安全中断 M-mode 使能, S-mode 关闭

/*set M/S mode, M:1,S;0, or M:0,S:1*/
ie_val = plic_get_ie(plic, plic_hartid2context[hartid][!!mode], int_src_idx);
ie_val |= 1 << (int_id % 32);
plic_set_ie(plic, plic_hartid2context[hartid][!!mode], int_src_idx, ie_val);

ie_val = plic_get_ie(plic, plic_hartid2context[hartid][!mode], int_src_idx);
ie_val &= ~(1 << (int_id % 32));
plic_set_ie(plic, plic_hartid2context[hartid][!mode], int_src_idx, ie_val);

return 0;
}

void plic_set_ie(struct plic_data *plic, u32 cntxid, u32 word_index, u32 val)
{
plic_ie = (void *)plic->addr +
PLIC_ENABLE_BASE + PLIC_ENABLE_STRIDE * cntxid;
writel(val, plic_ie + word_index * 4);
}

与 arm 差异

维护中断是否是安全中断/非安全中断

  • nuclei riscv 哪个中断号是安全的, 哪个是不安全的, 由软件维护
  • arm 由设置 gic group 寄存器确定了, 然后硬件维护中断状态, 根据运行模式将中断绑定到 fiq 或 irq
    中断的处理:
  • nuclei riscv , optee 只处理安全中断, ree 只处理非安全中断, M-mode opensbi 处理剩下的, 因为在运行到 tee 时, 非安全中断的 S-mode ie 关闭了, 在运行到 ree 时, 安全中断在 S-mode ie 被关闭了
  • arm 上, tee ree 还是会处理 irq fiq, 需要陷入到 monitor 或 EL3 转给对方.

RISC-V Better Atomics (Load-Acquire/Store-Release)

是 RISC-V 体系结构的一种扩展,旨在提供更强大的原子操作支持。
在并发编程中,原子操作是一种确保多个线程或处理器可以安全地对共享变量进行操作的机制。原子操作需要满足一定的一致性和同步性质,以避免数据竞争和不确定的行为。

RISC-V Better Atomics 扩展引入了一对新的原子指令,即 Load-Acquire 和 Store-Release。

  • Load-Acquire 用于读取共享变量,并确保读取操作具有”acquire”语义,即保证在此原子操作之前的所有存储操作都可见。
  • Store-Release 用于写入共享变量,并确保写入操作具有”release”语义,即保证在此原子操作之后的所有加载操作都能看到最新的值。

使用 RISC-V Better Atomics 可以更好地控制并发访问共享变量的顺序和一致性。它提供了更细粒度的原子操作语义,以满足高性能和并发编程的需求。

在 RISCV-V 的 A 扩展下

  • Load-Acquire 指令通常以 lr.amoswap 的形式出现,例如 lr.w(Load-Reserved Word)用于加载一个字(32位数据)并获取内存序。
  • Store-Release 指令通常以 sc.amoswap 的形式出现,例如 sc.w(Store-Conditional Word)用于将一个字(32位数据)存储到内存中并释放内存序。

Saturating Operations

饱和操作(Saturating Operations)是一种特殊的操作,用于处理数值溢出的情况。当执行某种算术操作时,如果结果超出了数据类型的表示范围,饱和操作将会将结果截断为数据类型所能表示的最大或最小值,而不是简单地截断为溢出的结果。

RISC-V 架构中的饱和操作主要应用于整数数据类型。以下是一些常见的饱和操作指令:

  1. SLL.SAT:饱和左移指令,将一个寄存器中的整数左移指定的位数,并将结果截断为数据类型的最大或最小值。
  2. SRA.SAT:饱和右算术移位指令,将一个寄存器中的整数右算术移位指定的位数,并将结果截断为数据类型的最大或最小值。
  3. SRL.SAT:饱和右逻辑移位指令,将一个寄存器中的整数右逻辑移位指定的位数,并将结果截断为数据类型的最大或最小值。
  4. ADD.SAT:饱和加法指令,将两个寄存器中的整数相加,并将结果截断为数据类型的最大或最小值。
  5. SUB.SAT:饱和减法指令,将两个寄存器中的整数相减,并将结果截断为数据类型的最大或最小值。

这些饱和操作指令使得处理数值溢出的情况更加方便和可控,避免了溢出错误对程序执行的影响。

需要注意的是,饱和操作并不是所有 RISC-V 架构中都支持的标准指令。具体支持的指令和操作会根据特定的处理器和实现而有所不同。因此,在编写程序时,建议参考相关的处理器手册或编程指南,以了解特定处理器对饱和操作的支持情况和具体的指令格式。

Half-width floats (Zfh)

半精度浮点数(Half-width floats)是一种浮点数表示格式,用于表示较小的浮点数范围。在 RISC-V 中,半精度浮点数采用 IEEE 754 标准的半精度浮点格式。

半精度浮点数使用 16 位(2 字节)来表示一个浮点数,其中包括符号位、指数位和尾数位。具体的格式如下:

  • 符号位(1 位):用于表示浮点数的正负。
  • 指数位(5 位):用于表示浮点数的指数部分,可以表示范围为 -14 到 +15。
  • 尾数位(10 位):用于表示浮点数的尾数部分。

半精度浮点数相比于单精度浮点数(32 位)和双精度浮点数(64 位)具有较小的范围和精度,但占用更少的存储空间。在一些资源受限的场景中,如嵌入式系统或移动设备,半精度浮点数可以用于节省存储空间和提高计算效率。

玄铁 910 支持半精度浮点类指令

Bitmanip

RISC-V Bitmanip 扩展是 RISC-V 指令集架构的一部分,用于进行位操作和位操作相关的操作。Bitmanip 扩展引入了一组指令,以提供对位操作的支持,包括位移、位计数、位反转、位扩展等功能。

Bitmanip 扩展的主要目的是增强 RISC-V 的位操作能力,使开发者能够更高效地处理位级操作和位级数据操作。这对于一些应用场景,如加密算法、图形处理、数据压缩等,非常有用.

Bitmanip 扩展引入了一些常用的位操作指令,例如:

  • clz: 计算无符号整数的前导零位数。
  • ctz: 计算无符号整数的尾部零位数。
  • pcnt: 计算无符号整数的位计数,即二进制表示中的位为 1 的个数。
  • slo: 逻辑左移操作,将指定位数的位从右边移入。
  • sro: 逻辑右移操作,将指定位数的位从左边移入。
  • rol: 循环左移操作,将位向左循环移动。
  • ror: 循环右移操作,将位向右循环移动。

这些指令使得开发者能够更高效地进行位级操作和位级数据处理,简化代码编写和提高运行效率。

需要注意的是,具体的 Bitmanip 指令和功能会根据 RISC-V 架构扩展的版本和实现而有所不同。因此,建议查阅相关的处理器手册或编程指南以获取详细的信息和指令使用示例。

玄铁 C910 支持扩展的位操作指令

J extension

RISC-V J 的扩展旨在使 RISC-V 成为传统解释或 JIT 编译的语言或需要大型运行时库或语言级虚拟机的语言的一个有吸引力的目标, 包括 C# JAVA python 等.

https://github.com/riscv/riscv-j-extension

RISC-V 指针屏蔽(PM)是一个功能,当启用时,会导致 MMU 忽略有效地址的前 N 位。使得这些比特可以以应用程序选择的任何方式使用。所描述的扩展版本专门针对标签检查。当一个地址被访问时,存储在被屏蔽位中的标签与基于范围的标签进行比较。这被用于动态安全检查器,如 HWASAN[1]。这样的工具可以应用于所有的特权模式(U、S 和 M)。

ABI gaps

需要 TLSDESC 等

TLSDESC

RISC-V TLSDESC,全称为 Thread Local Storage Descriptor,是 RISC-V 指令集架构的一部分,用于支持线程本地存储(Thread Local Storage,TLS)的访问。

TLS 是一种机制,允许每个线程在共享内存的基础上拥有自己独立的数据区域,这对于多线程编程非常重要。TLS 可以用于存储线程特定的数据,例如线程的局部变量或全局状态。

RISC-V TLSDESC 扩展引入了以下指令:

TLSDESC_CALL (TLS Descriptor Call):调用 TLS 描述符以获取线程本地存储数据的地址。
TLS 描述符是一个特殊的数据结构,用于获取线程本地存储数据的地址。TLSDESC_CALL 指令用于调用 TLS 描述符,并将返回的地址存储在指定的寄存器中,以便后续访问线程本地存储数据。

需要注意的是,RISC-V TLSDESC 扩展是可选的,并不是所有的 RISC-V 架构都支持该扩展。具体的支持情况可以参考特定处理器的文档或规格说明。

TLSDESC 扩展的引入使得在 RISC-V 架构上能够更方便地使用线程本地存储,提供了对多线程编程的支持

arm 中通过 TPIDR_EL0 寄存器,每个线程可以访问自己的 TLS 数据,而不需要使用全局变量或其他机制。线程局部存储是一种为每个线程分配独立内存空间的机制,用于保存线程特定的数据。
通过将 TLS 基址存储在 TPIDR_EL0 寄存器中,线程可以通过读取该寄存器来迅速获取自己的 TLS 数据的访问地址。

HWASAN

(Hardware-assisted Address Sanitizer)是一种基于硬件辅助的地址检测工具,用于检测和调试软件中的内存错误和安全问题。它类似于其他架构上的 AddressSanitizer(ASan)工具,但专门针对 RISC-V 架构进行了优化。

该实现依赖于 J-extension(又称 “指针屏蔽)的可用性。目前,这个扩展不是官方的
需要修改编译器 (llvm) 适配该实现

TEE

there’s an Ever ratcheting bar on the hardware features that are required whether it’s trusted execution environments confidential compute virtualization or even runtime detection of memory safety errors in the hardware

需要虚拟化实现的 TEE 或者硬件内存安全错误检测机制 (如 ARM 的 trustzone 或 sifive 的 worldguard 机制)

玄铁 c910 支持 VirtualZone 技术 (PMP+IOPMP)

virtualization

硬件虚拟化, 可以用来辅助实现内存的安全隔离机制
trusted execution environments confidential compute virtualization

C910 extension 小结

nuclei sdk

目录结构

1
2
3
4
5
6
7
8
9
10
11
nuclei-sdk
├── Build #build 生成目录
├── Components # 空目录
├── NMSIS # dsp nn的静态库和头文件
├── OS # FreeRTOS RTThread ucosii 头文件及源码目录
├── SoC # demosoc gd32vf103 bsp 头文件及源码目录
├── application # baremental FreeRTOS RTThread ucosii 下跑的app 源码
├── doc # 文档
├── logs # 测试框架生成目录
├── test # riscv 指令集 中断 fpu pmp timer 等相关测试项, 在baremental下的测试
└── tools # 测试框架脚本

编译baremental app

例子

1
make PROGRAM=application/baremetal/helloworld SOC=demosoc BOARD=nuclei_fpga_eval all

运行baremental app

例子

1
nuclei_qemu/bin/qemu-system-riscv64  -M nuclei_n -cpu nuclei-ux600 -nodefaults -nographic -serial stdio -kernel application/baremetal/helloworld/helloworld.elf

riscv isa 测试框架

指test下的对riscv isa的测试

主要的文件在 test/core/* 下.
编译时根据 指定的 SOC 指定使用的bsp的代码
如SOC 指定 demosoc
使用的 SoC/demosoc/Common/ 下的源文件
BOARD指定为nuclei_fpga_eval
引用的头文件 SoC/demosoc/Board/nuclei_fpga_eval

1
2
3
4
5
"-I/home/liguang/work_space/nuclei-sdk/test/core",
"-I/home/liguang/work_space/nuclei-sdk/test",
"-I/home/liguang/work_space/nuclei-sdk/NMSIS/Core/Include",
"-I/home/liguang/work_space/nuclei-sdk/SoC/demosoc/Board/nuclei_fpga_eval/Include",
"-I/home/liguang/work_space/nuclei-sdk/SoC/demosoc/Common/Include"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
SoC/demosoc/Common/Source/GCC/intexc_demosoc.S, # 中断 trap 上下文切换 相关
SoC/demosoc/Common/Source/GCC/startup_demosoc.S, #bsp 初始启动相关源码
SoC/demosoc/Common/Source/Drivers/demosoc_gpio.c, # gpio
SoC/demosoc/Common/Source/Drivers/demosoc_uart.c, # uart
SoC/demosoc/Common/Source/Stubs/newlib/clock_getres.c, #clock time
SoC/demosoc/Common/Source/Stubs/newlib/clock_gettime.c,
SoC/demosoc/Common/Source/Stubs/newlib/clock_settime.c,
SoC/demosoc/Common/Source/Stubs/newlib/close.c, #文件相关, 大部分是空的桩函数
SoC/demosoc/Common/Source/Stubs/newlib/execve.c,
SoC/demosoc/Common/Source/Stubs/newlib/exit.c,
SoC/demosoc/Common/Source/Stubs/newlib/fork.c,
SoC/demosoc/Common/Source/Stubs/newlib/fstat.c,
SoC/demosoc/Common/Source/Stubs/newlib/getpid.c,
SoC/demosoc/Common/Source/Stubs/newlib/gettimeofday.c,
SoC/demosoc/Common/Source/Stubs/newlib/isatty.c,
SoC/demosoc/Common/Source/Stubs/newlib/kill.c,
SoC/demosoc/Common/Source/Stubs/newlib/link.c,
SoC/demosoc/Common/Source/Stubs/newlib/lseek.c,
SoC/demosoc/Common/Source/Stubs/newlib/open.c,
SoC/demosoc/Common/Source/Stubs/newlib/read.c,
SoC/demosoc/Common/Source/Stubs/newlib/sbrk.c,
SoC/demosoc/Common/Source/Stubs/newlib/stat.c,
SoC/demosoc/Common/Source/Stubs/newlib/times.c,
SoC/demosoc/Common/Source/Stubs/newlib/unlink.c,
SoC/demosoc/Common/Source/Stubs/newlib/wait.c,
SoC/demosoc/Common/Source/Stubs/newlib/write.c,
SoC/demosoc/Common/Source/demosoc_common.c, # delay_1ms get_cpu_freq measure_cpu_freq
# 大部分为startup.S 中的c 函数实现 SystemInit system_default_exception_handler _premain_init _postmain_fini 等
SoC/demosoc/Common/Source/system_demosoc.c,
test/core/main.c,
test/core/test_atomic.c,
test/core/test_compiler.c,
test/core/test_csr.c,
test/core/test_eclic.c,
test/core/test_fpu.c,
test/core/test_pmp.c,
test/core/test_timer.c

执行原理

ctest_main
从 .data.ctest 内存开始的地方进行扫描, 扫描 magic 为 CTEST_IMPL_MAGIC 的 struct ctest 数据
执行其 run 方法.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
                    if (test->setup && *test->setup) (*test->setup)(test->data);
if (test->data)
test->run(test->data);
else
test->run();

#define CTEST(sname, tname) CTEST_IMPL_CTEST(sname, tname, 0)
#define CTEST_IMPL_CTEST(sname, tname, tskip) \
static void CTEST_IMPL_FNAME(sname, tname)(void); \
CTEST_IMPL_STRUCT(sname, tname, tskip, NULL, NULL, NULL); \
static void CTEST_IMPL_FNAME(sname, tname)(void)

#define CTEST_IMPL_SECTION __attribute__ ((used, section (".data.ctest"), aligned(1)))

#define CTEST_IMPL_STRUCT(sname, tname, tskip, tdata, tsetup, tteardown) \
static struct ctest CTEST_IMPL_TNAME(sname, tname) CTEST_IMPL_SECTION = { \
.ssname=#sname, \
.ttname=#tname, \
.run = CTEST_IMPL_FNAME(sname, tname), \
.data = tdata, \
.setup = (ctest_setup_func*) tsetup, \
.teardown = (ctest_teardown_func*) tteardown, \
.skip = tskip, \
.magic = CTEST_IMPL_MAGIC }
ex:
CTEST(pmp, pmpreg)
run = ctest_pmp_pmpreg_run

跑测

芯来自动测试框架

ci 构建
.gitlab-ci.yml

tools/script 下封装了大量跑测全部工程的脚本.
待了解, 不是本次调研的重点.

如根据 appcfg hwcfg 选择指定的SOC/BOARD/CORE 跑测对应的 application

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
"run_config": {
"target" : "qemu",
"hardware" : {
"baudrate": 115200,
"timeout": 60
},
"qemu" : {
"timeout": 60
}
},
"build_target": "clean all",
"build_config": {
"SOC": "demosoc",
"BOARD": "nuclei_fpga_eval",
"CORE": "ux600",
"DOWNLOAD": "ilm",
"ARCH_EXT": ""
},
1
2
3
4
# build 构建脚本
python3 tools/scripts/nsdk_cli/nsdk_bench.py --appcfg tools/scripts/nsdk_cli/configs/application.json --hwcfg tools/scripts/nsdk_cli/configs/nuclei_fpga_eval_qemu.json --parallel="-j" --logdir logs/nuclei_fpga_eval_ci
# 跑测脚本
python3 tools/scripts/nsdk_cli/nsdk_bench.py --appcfg tools/scripts/nsdk_cli/configs/application.json --hwcfg tools/scripts/nsdk_cli/configs/nuclei_fpga_eval_qemu.json --parallel="-j" --logdir logs/nuclei_fpga_eval_ci --run

上述跑测是用qemu测的

生成的报告

部分报告
report.md

xlspike 跑测

1
2
3
4
5
6
7
tools/scripts/misc/dobench/run.sh

...
Build command: make -C application/baremetal/benchmark/coremark SOC=demosoc BOARD=nuclei_fpga_eval DOWNLOAD=ilm CPU_SERIES=200 SIMULATION=1 SIMU=xlspike CORE=n203e showflags

Run application on xlspike
...

这个脚本也是用 nsdk_bench.py 跑的, 只是模拟器换成了 xlspike

1
2
3
4
python3 $NSDK_BENCH_PY --appcfg $appcfg --hwcfg $hwcfg --parallel=-j --logdir $logdir --make_options \"$SIMU_OPTS 
$mkopts\" $RUN_OPTS

python3 /home/liguang/work_space/nuclei-sdk/tools/scripts/nsdk_cli/nsdk_bench.py --appcfg /home/liguang/work_space/nuclei-sdk/tools/scripts/misc/dobench/app.json --hwcfg /home/liguang/work_space/nuclei-sdk/tools/scripts/misc/dobench/bench_nx600.json --parallel=-j --logdir /home/liguang/work_space/nuclei-sdk/tools/scripts/misc/dobench/gen/0.3.8-1-g9cfda9b1fa-dirty/dobench//barebench/nx600 --make_options "SIMULATION=1 SIMU=xlspike " --run --run_target xlspike

总共为 n200/n300/n600/n900/nx600/nx900 的 application/baremetal/benchmark下的 coremark dhrystone whetstone
编译了json 配置的 指令集的elf, 并用nuclei的 xl_spike 进行跑测.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
"rv32emc": {"CORE":"n203e"},
"rv32imac": {"CORE":"n203"},
"rv32imacb": {"CORE":"n203", "ARCH_EXT":"b"}
...
"rv32imac": {"CORE":"n300"},
"rv32imacb": {"CORE":"n300", "ARCH_EXT":"b"},
"rv32imacbp": {"CORE":"n300", "ARCH_EXT":"bp"},
"rv32imafc": {"CORE":"n300f", "ARCH_EXT":""},
"rv32imafcb": {"CORE":"n300f", "ARCH_EXT":"b"},
"rv32imafcp": {"CORE":"n300f", "ARCH_EXT":"p"},
"rv32imafcbp": {"CORE":"n300f", "ARCH_EXT":"bp"},
"rv32imafdc": {"CORE":"n300fd", "ARCH_EXT":""},
"rv32imafdcb": {"CORE":"n300fd", "ARCH_EXT":"b"},
"rv32imafdcp": {"CORE":"n300fd", "ARCH_EXT":"p"},
"rv32imafdcbp": {"CORE":"n300fd", "ARCH_EXT":"bp"}
...
"rv64imac": {"CORE":"nx600"},
"rv64imacb": {"CORE":"nx600", "ARCH_EXT":"b"},
"rv64imacbp": {"CORE":"nx600", "ARCH_EXT":"bp"},
"rv64imafc": {"CORE":"nx600f", "ARCH_EXT":""},
"rv64imafcb": {"CORE":"nx600f", "ARCH_EXT":"b"},
"rv64imafcp": {"CORE":"nx600f", "ARCH_EXT":"p"},
"rv64imafcbp": {"CORE":"nx600f", "ARCH_EXT":"bp"},
"rv64imafdc": {"CORE":"nx600fd", "ARCH_EXT":""},
"rv64imafdcb": {"CORE":"nx600fd", "ARCH_EXT":"b"},
"rv64imafdcp": {"CORE":"nx600fd", "ARCH_EXT":"p"},
"rv64imafdcbp": {"CORE":"nx600fd", "ARCH_EXT":"bp"}

其中的一条指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Build application application/baremetal/benchmark/dhrystone, with target: clean
Build command: make -j -C application/baremetal/benchmark/dhrystone SOC=demosoc BOARD=nuclei_fpga_eval DOWNLOAD=ilm CPU_SERIES=600 SIMULATION=1 SIMU=xlspike DHRY_MODE=inline CORE=nx600fd ARCH_EXT=bp clean
Build command return value: 0
Build application application/baremetal/benchmark/dhrystone, with target: dasm
Build command: make -j -C application/baremetal/benchmark/dhrystone SOC=demosoc BOARD=nuclei_fpga_eval DOWNLOAD=ilm CPU_SERIES=600 SIMULATION=1 SIMU=xlspike DHRY_MODE=inline CORE=nx600fd ARCH_EXT=bp dasm
Build command return value: 0
Build application application/baremetal/benchmark/dhrystone, with target: showtoolver
Build command: make -C application/baremetal/benchmark/dhrystone SOC=demosoc BOARD=nuclei_fpga_eval DOWNLOAD=ilm CPU_SERIES=600 SIMULATION=1 SIMU=xlspike DHRY_MODE=inline CORE=nx600fd ARCH_EXT=bp showtoolver
Build command return value: 0
Build application application/baremetal/benchmark/dhrystone, with target: showflags
Build command: make -C application/baremetal/benchmark/dhrystone SOC=demosoc BOARD=nuclei_fpga_eval DOWNLOAD=ilm CPU_SERIES=600 SIMULATION=1 SIMU=xlspike DHRY_MODE=inline CORE=nx600fd ARCH_EXT=bp showflags
Build command return value: 0
Build application application/baremetal/benchmark/dhrystone, time cost 0.73 seconds, passed: True
Run application on xlspike
xl_spike doesn't exist in PATH, please check!

   * - dasm

     - build and dissemble application with selected configuration
  - add ``showflags`` target to show compiling information and flags
- add ``showtoolver`` target to show tool version used
     

总共生成的文件列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
├── allcfg.json
├── allrst.json
├── app_failed.txt
├── app_passed.txt
├── n200
│   ├── app_failed.txt
│   ├── app_passed.txt
│   ├── appcfg.json
│   ├── application
│   │   └── baremetal
│   │   └── benchmark
│   │   ├── coremark
│   │   │   ├── rv32emc
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv32imac
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   └── rv32imacb
│   │   │   ├── build.log
│   │   │   ├── coremark.dasm
│   │   │   ├── coremark.elf
│   │   │   ├── coremark.map
│   │   │   └── coremark.verilog
│   │   ├── dhrystone
│   │   │   ├── rv32emc
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv32imac
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   └── rv32imacb
│   │   │   ├── build.log
│   │   │   ├── dhrystone.dasm
│   │   │   ├── dhrystone.elf
│   │   │   ├── dhrystone.map
│   │   │   └── dhrystone.verilog
│   │   └── whetstone
│   │   ├── rv32emc
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv32imac
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   └── rv32imacb
│   │   ├── build.log
│   │   ├── whetstone.dasm
│   │   ├── whetstone.elf
│   │   ├── whetstone.map
│   │   └── whetstone.verilog
│   ├── hwcfg.json
│   ├── mergedcfg.json
│   ├── report.html
│   ├── report.md
│   ├── result.csv
│   ├── result.json
│   ├── runresult.json
│   ├── runresult.xlsx
│   ├── runresult.xlsx.csvdict.json
│   └── runresult.xlsx.csvtable.json
├── n300
│   ├── app_failed.txt
│   ├── app_passed.txt
│   ├── appcfg.json
│   ├── application
│   │   └── baremetal
│   │   └── benchmark
│   │   ├── coremark
│   │   │   ├── rv32imac
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv32imacb
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv32imacbp
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv32imafc
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv32imafcb
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv32imafcbp
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv32imafcp
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv32imafdc
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv32imafdcb
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv32imafdcbp
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   └── rv32imafdcp
│   │   │   ├── build.log
│   │   │   ├── coremark.dasm
│   │   │   ├── coremark.elf
│   │   │   ├── coremark.map
│   │   │   └── coremark.verilog
│   │   ├── dhrystone
│   │   │   ├── rv32imac
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv32imacb
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv32imacbp
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv32imafc
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv32imafcb
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv32imafcbp
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv32imafcp
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv32imafdc
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv32imafdcb
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv32imafdcbp
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   └── rv32imafdcp
│   │   │   ├── build.log
│   │   │   ├── dhrystone.dasm
│   │   │   ├── dhrystone.elf
│   │   │   ├── dhrystone.map
│   │   │   └── dhrystone.verilog
│   │   └── whetstone
│   │   ├── rv32imac
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv32imacb
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv32imacbp
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv32imafc
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv32imafcb
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv32imafcbp
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv32imafcp
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv32imafdc
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv32imafdcb
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv32imafdcbp
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   └── rv32imafdcp
│   │   ├── build.log
│   │   ├── whetstone.dasm
│   │   ├── whetstone.elf
│   │   ├── whetstone.map
│   │   └── whetstone.verilog
│   ├── hwcfg.json
│   ├── mergedcfg.json
│   ├── report.html
│   ├── report.md
│   ├── result.csv
│   ├── result.json
│   ├── runresult.json
│   ├── runresult.xlsx
│   ├── runresult.xlsx.csvdict.json
│   └── runresult.xlsx.csvtable.json
├── n600
│   ├── app_failed.txt
│   ├── app_passed.txt
│   ├── appcfg.json
│   ├── application
│   │   └── baremetal
│   │   └── benchmark
│   │   ├── coremark
│   │   │   ├── rv32imac
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv32imacb
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv32imacbp
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv32imafc
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv32imafcb
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv32imafcbp
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv32imafcp
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv32imafdc
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv32imafdcb
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv32imafdcbp
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   └── rv32imafdcp
│   │   │   ├── build.log
│   │   │   ├── coremark.dasm
│   │   │   ├── coremark.elf
│   │   │   ├── coremark.map
│   │   │   └── coremark.verilog
│   │   ├── dhrystone
│   │   │   ├── rv32imac
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv32imacb
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv32imacbp
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv32imafc
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv32imafcb
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv32imafcbp
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv32imafcp
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv32imafdc
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv32imafdcb
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv32imafdcbp
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   └── rv32imafdcp
│   │   │   ├── build.log
│   │   │   ├── dhrystone.dasm
│   │   │   ├── dhrystone.elf
│   │   │   ├── dhrystone.map
│   │   │   └── dhrystone.verilog
│   │   └── whetstone
│   │   ├── rv32imac
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv32imacb
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv32imacbp
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv32imafc
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv32imafcb
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv32imafcbp
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv32imafcp
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv32imafdc
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv32imafdcb
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv32imafdcbp
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   └── rv32imafdcp
│   │   ├── build.log
│   │   ├── whetstone.dasm
│   │   ├── whetstone.elf
│   │   ├── whetstone.map
│   │   └── whetstone.verilog
│   ├── hwcfg.json
│   ├── mergedcfg.json
│   ├── report.html
│   ├── report.md
│   ├── result.csv
│   ├── result.json
│   ├── runresult.json
│   ├── runresult.xlsx
│   ├── runresult.xlsx.csvdict.json
│   └── runresult.xlsx.csvtable.json
├── n900
│   ├── app_failed.txt
│   ├── app_passed.txt
│   ├── appcfg.json
│   ├── application
│   │   └── baremetal
│   │   └── benchmark
│   │   ├── coremark
│   │   │   ├── rv32imac
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv32imacb
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv32imacbp
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv32imafc
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv32imafcb
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv32imafcbp
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv32imafcp
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv32imafdc
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv32imafdcb
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv32imafdcbp
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   └── rv32imafdcp
│   │   │   ├── build.log
│   │   │   ├── coremark.dasm
│   │   │   ├── coremark.elf
│   │   │   ├── coremark.map
│   │   │   └── coremark.verilog
│   │   ├── dhrystone
│   │   │   ├── rv32imac
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv32imacb
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv32imacbp
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv32imafc
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv32imafcb
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv32imafcbp
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv32imafcp
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv32imafdc
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv32imafdcb
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv32imafdcbp
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   └── rv32imafdcp
│   │   │   ├── build.log
│   │   │   ├── dhrystone.dasm
│   │   │   ├── dhrystone.elf
│   │   │   ├── dhrystone.map
│   │   │   └── dhrystone.verilog
│   │   └── whetstone
│   │   ├── rv32imac
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv32imacb
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv32imacbp
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv32imafc
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv32imafcb
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv32imafcbp
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv32imafcp
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv32imafdc
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv32imafdcb
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv32imafdcbp
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   └── rv32imafdcp
│   │   ├── build.log
│   │   ├── whetstone.dasm
│   │   ├── whetstone.elf
│   │   ├── whetstone.map
│   │   └── whetstone.verilog
│   ├── hwcfg.json
│   ├── mergedcfg.json
│   ├── report.html
│   ├── report.md
│   ├── result.csv
│   ├── result.json
│   ├── runresult.json
│   ├── runresult.xlsx
│   ├── runresult.xlsx.csvdict.json
│   └── runresult.xlsx.csvtable.json
├── nx600
│   ├── app_failed.txt
│   ├── app_passed.txt
│   ├── appcfg.json
│   ├── application
│   │   └── baremetal
│   │   └── benchmark
│   │   ├── coremark
│   │   │   ├── rv64imac
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv64imacb
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv64imacbp
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv64imafc
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv64imafcb
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv64imafcbp
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv64imafcp
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv64imafdc
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv64imafdcb
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv64imafdcbp
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   └── rv64imafdcp
│   │   │   ├── build.log
│   │   │   ├── coremark.dasm
│   │   │   ├── coremark.elf
│   │   │   ├── coremark.map
│   │   │   └── coremark.verilog
│   │   ├── dhrystone
│   │   │   ├── rv64imac
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv64imacb
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv64imacbp
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv64imafc
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv64imafcb
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv64imafcbp
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv64imafcp
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv64imafdc
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv64imafdcb
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv64imafdcbp
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   └── rv64imafdcp
│   │   │   ├── build.log
│   │   │   ├── dhrystone.dasm
│   │   │   ├── dhrystone.elf
│   │   │   ├── dhrystone.map
│   │   │   └── dhrystone.verilog
│   │   └── whetstone
│   │   ├── rv64imac
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv64imacb
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv64imacbp
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv64imafc
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv64imafcb
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv64imafcbp
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv64imafcp
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv64imafdc
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv64imafdcb
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv64imafdcbp
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   └── rv64imafdcp
│   │   ├── build.log
│   │   ├── whetstone.dasm
│   │   ├── whetstone.elf
│   │   ├── whetstone.map
│   │   └── whetstone.verilog
│   ├── hwcfg.json
│   ├── mergedcfg.json
│   ├── report.html
│   ├── report.md
│   ├── result.csv
│   ├── result.json
│   ├── runresult.json
│   ├── runresult.xlsx
│   ├── runresult.xlsx.csvdict.json
│   └── runresult.xlsx.csvtable.json
├── nx900
│   ├── app_failed.txt
│   ├── app_passed.txt
│   ├── appcfg.json
│   ├── application
│   │   └── baremetal
│   │   └── benchmark
│   │   ├── coremark
│   │   │   ├── rv64imac
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv64imacb
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv64imacbp
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv64imafc
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv64imafcb
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv64imafcbp
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv64imafcp
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv64imafdc
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv64imafdcb
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   ├── rv64imafdcbp
│   │   │   │   ├── build.log
│   │   │   │   ├── coremark.dasm
│   │   │   │   ├── coremark.elf
│   │   │   │   ├── coremark.map
│   │   │   │   └── coremark.verilog
│   │   │   └── rv64imafdcp
│   │   │   ├── build.log
│   │   │   ├── coremark.dasm
│   │   │   ├── coremark.elf
│   │   │   ├── coremark.map
│   │   │   └── coremark.verilog
│   │   ├── dhrystone
│   │   │   ├── rv64imac
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv64imacb
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv64imacbp
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv64imafc
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv64imafcb
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv64imafcbp
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv64imafcp
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv64imafdc
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv64imafdcb
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   ├── rv64imafdcbp
│   │   │   │   ├── build.log
│   │   │   │   ├── dhrystone.dasm
│   │   │   │   ├── dhrystone.elf
│   │   │   │   ├── dhrystone.map
│   │   │   │   └── dhrystone.verilog
│   │   │   └── rv64imafdcp
│   │   │   ├── build.log
│   │   │   ├── dhrystone.dasm
│   │   │   ├── dhrystone.elf
│   │   │   ├── dhrystone.map
│   │   │   └── dhrystone.verilog
│   │   └── whetstone
│   │   ├── rv64imac
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv64imacb
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv64imacbp
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv64imafc
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv64imafcb
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv64imafcbp
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv64imafcp
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv64imafdc
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv64imafdcb
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   ├── rv64imafdcbp
│   │   │   ├── build.log
│   │   │   ├── whetstone.dasm
│   │   │   ├── whetstone.elf
│   │   │   ├── whetstone.map
│   │   │   └── whetstone.verilog
│   │   └── rv64imafdcp
│   │   ├── build.log
│   │   ├── whetstone.dasm
│   │   ├── whetstone.elf
│   │   ├── whetstone.map
│   │   └── whetstone.verilog
│   ├── hwcfg.json
│   ├── mergedcfg.json
│   ├── report.html
│   ├── report.md
│   ├── result.csv
│   ├── result.json
│   ├── runresult.json
│   ├── runresult.xlsx
│   ├── runresult.xlsx.csvdict.json
│   └── runresult.xlsx.csvtable.json
├── report.html
├── report.md
├── result.csv
├── runresult.json
├── runresult.xlsx
├── runresult.xlsx.csvdict.json
└── runresult.xlsx.csvtable.json

riscv-tests

https://github.com/riscv-software-src/riscv-tests

目录结构

1
2
3
4
5
6
├── benchmarks # benchmarks 
├── debug # cpu gdb 测试环境
├── env #python env 环境
├── isa # 指令集测试
├── mt # 矩阵乘法测试
└── target # 生成目录

gdb 测试环境

debug 下的测试主要是针对cpu本身的测试, cpu 设置好初始环境后进行loop, 然后通过gdb 执行对应的汇编指令跑测, 判断测试结构是否符合预期.

这部分主要包含两个部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
targets
├── RISC-V
│ ├── __pycache__
│ │ ├── spike32.cpython-39.pyc
│ │ └── spike64.cpython-39.pyc
│ ├── spike-1.cfg
│ ├── spike-2-hwthread.cfg
│ ├── spike-2.cfg
│ ├── spike-multi.cfg
│ ├── spike-multi.py
│ ├── spike32-2-hwthread.py
│ ├── spike32-2.py
│ ├── spike32.lds
│ ├── spike32.py
│ ├── spike64-2-hwthread.py
│ ├── spike64-2-rtos.py
│ ├── spike64-2.py
│ ├── spike64.lds
│ └── spike64.py
└── SiFive
├── Freedom
│ ├── E300.py
│ ├── Freedom.cfg
│ ├── Freedom.lds
│ ├── U500.py
│ └── U500Sim.py
├── HiFive1-flash.lds
├── HiFive1-flash.py
├── HiFive1.cfg
├── HiFive1.lds
├── HiFive1.py
├── HiFiveUnleashed-flash.lds
├── HiFiveUnleashed-flash.py
├── HiFiveUnleashed.cfg
├── HiFiveUnleashed.lds
├── HiFiveUnleashed.py
└── HiFiveUnleashed_setup.bin

模拟运行:

通过openocd + spike 测试

https://github.com/riscv-software-src/riscv-isa-sim

1
2
3
4
5
6
7
8
9
10
$ spike --rbb-port=9824 -m0x10100000:0x20000 rot13-64
Listening for remote bitbang connection on port 9824.

$ openocd -f spike.cfg
Open On-Chip Debugger 0.10.0-dev-00002-gc3b344d (2017-06-08-12:14)
...
riscv.cpu: target state: halted

$ riscv64-unknown-elf-gdb
target remote localhost:3333

有条件下厂商需要定制 riscv 公版的spike, 如芯来的为 xl_spike, 没找到源码

跑测例子

1
2
3
4
5
6
./gdbserver.py ./targets/RISC-V/spike64.py MemTest8
Using $misa from hart definition: 0x8000000000141125
[MemTest8] Starting > logs/20230226-170843-spike64-MemTest8.log
[MemTest8] pass in 2.26s
::::::::::::::::::::::::::::[ ran 1 tests in 2s ]:::::::::::::::::::::::::::::
1 tests returned pass

解析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class SimpleMemoryTest(GdbTest):
def access_test(self, size, data_type):
assertEqual(self.gdb.p(f"sizeof({data_type})"), size)
a = 0x86753095555aaaa & ((1<<(size*8))-1)
b = 0xdeadbeef12345678 & ((1<<(size*8))-1)
addrA = self.hart.ram
addrB = self.hart.ram + self.hart.ram_size - size
self.gdb.p(f"*(({data_type}*)0x{addrA:x}) = 0x{a:x}") # 设置 *addrA = a
self.gdb.p(f"*(({data_type}*)0x{addrB:x}) = 0x{b:x}") # 设置 *addrB = b
assertEqual(self.gdb.p(f"*(({data_type}*)0x{addrA:x})"), a) # 判断 *ddrA 是否=a
assertEqual(self.gdb.p(f"*(({data_type}*)0x{addrB:x})"), b) # 判断 *ddrB 是否=b

class MemTest8(SimpleMemoryTest):
def test(self):
self.access_test(1, 'char')

拆解下这个测试干了哪些事情:

编译

最终生成的 elf 文件为 spike64_checksum-8000000000141125

1
riscv64-unknown-elf-gcc -g programs/checksum.c programs/tiny-malloc.c programs/infinite_loop.S -DDEFINE_MALLOC -DDEFINE_FREE programs/entry.S programs/init.c -DNHARTS=1 -I ../env -T targets/RISC-V/spike64.lds -nostartfiles -mcmodel=medany -DXLEN=64 -o spike64_checksum-8000000000141125 -march=rv64imafc -mabi=lp64

spike 运行

1
spike -p1 --isa RV64IMAFC --dm-auth --dm-progsize 0 --dm-sba 64 --dm-abstract-rti 30 -m0x1212340000:0x10000000 --rbb-port 0 spike64_checksum-8000000000141125

openocd

1
REMOTE_BITBANG_HOST=localhost REMOTE_BITBANG_PORT=34113 WORK_AREA=0x1212340000 USE_FREERTOS=0 openocd --command 'gdb_port 0' --command 'tcl_port disabled' --command 'telnet_port disabled' -f targets/RISC-V/spike-1.cfg

gdb执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
riscv64-unknown-elf-gdb \
...
monitor riscv reset_delays 127
target extended-remote localhost:40541
info threads
info threads
Id Target Id Frame
* 1 Remote target 0x00000012123406ae in ?? ()

p/x sizeof(char)
$1 = 0x1
p/x *((char*)0x1212340000) = 0xaa
p/x *((char*)0x122233ffff) = 0x78
p/x *((char*)0x1212340000)
0xaa
p/x *((char*)0x122233ffff)
0x78
Result: pass

fpga 运行:

通过 openocd + fpga 测试
这个没有跑测环境, 大概推测是由 fpga 运行对应的bin
openocd 连接调试环境, 通过gdb 连接openocd, 给fpga 发送cmd 进行测试

其他几个目录的测试

isa

为cpu指令集测试, 包含 S/U/M 模式下的 I, M, A, F, D, V测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
rv32mi-p-breakpoint
rv32mi-p-csr
rv32mi-p-illegal
rv32mi-p-lh-misaligned
rv32mi-p-lw-misaligned
rv32mi-p-ma_addr
rv32mi-p-ma_fetch
rv32mi-p-mcsr
rv32mi-p-sbreak
rv32mi-p-scall
rv32mi-p-sh-misaligned
rv32mi-p-shamt
rv32mi-p-sw-misaligned
rv32mi-p-zicntr
rv32si-p-csr
rv32si-p-dirty
rv32si-p-ma_fetch
rv32si-p-sbreak
rv32si-p-scall
rv32si-p-wfi
rv32ua-p-amoadd_w
rv32ua-p-amoand_w
rv32ua-p-amomax_w
rv32ua-p-amomaxu_w
rv32ua-p-amomin_w
rv32ua-p-amominu_w
rv32ua-p-amoor_w
rv32ua-p-amoswap_w
rv32ua-p-amoxor_w
rv32ua-p-lrsc
rv32ua-v-amoadd_w
rv32ua-v-amoand_w
rv32ua-v-amomax_w
rv32ua-v-amomaxu_w
rv32ua-v-amomin_w
rv32ua-v-amominu_w
rv32ua-v-amoor_w
rv32ua-v-amoswap_w
rv32ua-v-amoxor_w
rv32ua-v-lrsc
rv32uc-p-rvc
rv32uc-v-rvc
rv32ud-p-fadd
rv32ud-p-fclass
rv32ud-p-fcmp
rv32ud-p-fcvt
rv32ud-p-fcvt_w
rv32ud-p-fdiv
rv32ud-p-fmadd
rv32ud-p-fmin
rv32ud-p-ldst
rv32ud-p-recoding
rv32ud-v-fadd
rv32ud-v-fclass
rv32ud-v-fcmp
rv32ud-v-fcvt
rv32ud-v-fcvt_w
rv32ud-v-fdiv
rv32ud-v-fmadd
rv32ud-v-fmin
rv32ud-v-ldst
rv32ud-v-recoding
rv32uf-p-fadd
rv32uf-p-fclass
rv32uf-p-fcmp
rv32uf-p-fcvt
rv32uf-p-fcvt_w
rv32uf-p-fdiv
rv32uf-p-fmadd
rv32uf-p-fmin
rv32uf-p-ldst
rv32uf-p-move
rv32uf-p-recoding
rv32uf-v-fadd
rv32uf-v-fclass
rv32uf-v-fcmp
rv32uf-v-fcvt
rv32uf-v-fcvt_w
rv32uf-v-fdiv
rv32uf-v-fmadd
rv32uf-v-fmin
rv32uf-v-ldst
rv32uf-v-move
rv32uf-v-recoding
rv32ui-p-add
rv32ui-p-addi
rv32ui-p-and
rv32ui-p-andi
rv32ui-p-auipc
rv32ui-p-beq
rv32ui-p-bge
rv32ui-p-bgeu
rv32ui-p-blt
rv32ui-p-bltu
rv32ui-p-bne
rv32ui-p-fence_i
rv32ui-p-jal
rv32ui-p-jalr
rv32ui-p-lb
rv32ui-p-lbu
rv32ui-p-lh
rv32ui-p-lhu
rv32ui-p-lui
rv32ui-p-lw
rv32ui-p-or
rv32ui-p-ori
rv32ui-p-sb
rv32ui-p-sh
rv32ui-p-simple
rv32ui-p-sll
rv32ui-p-slli
rv32ui-p-slt
rv32ui-p-slti
rv32ui-p-sltiu
rv32ui-p-sltu
rv32ui-p-sra
rv32ui-p-srai
rv32ui-p-srl
rv32ui-p-srli
rv32ui-p-sub
rv32ui-p-sw
rv32ui-p-xor
rv32ui-p-xori
rv32ui-v-add
rv32ui-v-addi
rv32ui-v-and
rv32ui-v-andi
rv32ui-v-auipc
rv32ui-v-beq
rv32ui-v-bge
rv32ui-v-bgeu
rv32ui-v-blt
rv32ui-v-bltu
rv32ui-v-bne
rv32ui-v-fence_i
rv32ui-v-jal
rv32ui-v-jalr
rv32ui-v-lb
rv32ui-v-lbu
rv32ui-v-lh
rv32ui-v-lhu
rv32ui-v-lui
rv32ui-v-lw
rv32ui-v-or
rv32ui-v-ori
rv32ui-v-sb
rv32ui-v-sh
rv32ui-v-simple
rv32ui-v-sll
rv32ui-v-slli
rv32ui-v-slt
rv32ui-v-slti
rv32ui-v-sltiu
rv32ui-v-sltu
rv32ui-v-sra
rv32ui-v-srai
rv32ui-v-srl
rv32ui-v-srli
rv32ui-v-sub
rv32ui-v-sw
rv32ui-v-xor
rv32ui-v-xori
rv32um-p-div
rv32um-p-divu
rv32um-p-mul
rv32um-p-mulh
rv32um-p-mulhsu
rv32um-p-mulhu
rv32um-p-rem
rv32um-p-remu
rv32um-v-div
rv32um-v-divu
rv32um-v-mul
rv32um-v-mulh
rv32um-v-mulhsu
rv32um-v-mulhu
rv32um-v-rem
rv32um-v-remu
rv64mi-p-access
rv64mi-p-breakpoint
rv64mi-p-csr
rv64mi-p-illegal
rv64mi-p-ld-misaligned
rv64mi-p-lh-misaligned
rv64mi-p-lw-misaligned
rv64mi-p-ma_addr
rv64mi-p-ma_fetch
rv64mi-p-mcsr
rv64mi-p-sbreak
rv64mi-p-scall
rv64mi-p-sd-misaligned
rv64mi-p-sh-misaligned
rv64mi-p-sw-misaligned
rv64mi-p-zicntr
rv64si-p-csr
rv64si-p-dirty
rv64si-p-icache-alias
rv64si-p-ma_fetch
rv64si-p-sbreak
rv64si-p-scall
rv64si-p-wfi
rv64ssvnapot-p-napot
rv64ua-p-amoadd_d
rv64ua-p-amoadd_w
rv64ua-p-amoand_d
rv64ua-p-amoand_w
rv64ua-p-amomax_d
rv64ua-p-amomax_w
rv64ua-p-amomaxu_d
rv64ua-p-amomaxu_w
rv64ua-p-amomin_d
rv64ua-p-amomin_w
rv64ua-p-amominu_d
rv64ua-p-amominu_w
rv64ua-p-amoor_d
rv64ua-p-amoor_w
rv64ua-p-amoswap_d
rv64ua-p-amoswap_w
rv64ua-p-amoxor_d
rv64ua-p-amoxor_w
rv64ua-p-lrsc
rv64ua-v-amoadd_d
rv64ua-v-amoadd_w
rv64ua-v-amoand_d
rv64ua-v-amoand_w
rv64ua-v-amomax_d
rv64ua-v-amomax_w
rv64ua-v-amomaxu_d
rv64ua-v-amomaxu_w
rv64ua-v-amomin_d
rv64ua-v-amomin_w
rv64ua-v-amominu_d
rv64ua-v-amominu_w
rv64ua-v-amoor_d
rv64ua-v-amoor_w
rv64ua-v-amoswap_d
rv64ua-v-amoswap_w
rv64ua-v-amoxor_d
rv64ua-v-amoxor_w
rv64ua-v-lrsc
rv64uc-p-rvc
rv64uc-v-rvc
rv64ud-p-fadd
rv64ud-p-fclass
rv64ud-p-fcmp
rv64ud-p-fcvt
rv64ud-p-fcvt_w
rv64ud-p-fdiv
rv64ud-p-fmadd
rv64ud-p-fmin
rv64ud-p-ldst
rv64ud-p-move
rv64ud-p-recoding
rv64ud-p-structural
rv64ud-v-fadd
rv64ud-v-fclass
rv64ud-v-fcmp
rv64ud-v-fcvt
rv64ud-v-fcvt_w
rv64ud-v-fdiv
rv64ud-v-fmadd
rv64ud-v-fmin
rv64ud-v-ldst
rv64ud-v-move
rv64ud-v-recoding
rv64ud-v-structural
rv64uf-p-fadd
rv64uf-p-fclass
rv64uf-p-fcmp
rv64uf-p-fcvt
rv64uf-p-fcvt_w
rv64uf-p-fdiv
rv64uf-p-fmadd
rv64uf-p-fmin
rv64uf-p-ldst
rv64uf-p-move
rv64uf-p-recoding
rv64uf-v-fadd
rv64uf-v-fclass
rv64uf-v-fcmp
rv64uf-v-fcvt
rv64uf-v-fcvt_w
rv64uf-v-fdiv
rv64uf-v-fmadd
rv64uf-v-fmin
rv64uf-v-ldst
rv64uf-v-move
rv64uf-v-recoding
rv64ui-p-add
rv64ui-p-addi
rv64ui-p-addiw
rv64ui-p-addw
rv64ui-p-and
rv64ui-p-andi
rv64ui-p-auipc
rv64ui-p-beq
rv64ui-p-bge
rv64ui-p-bgeu
rv64ui-p-blt
rv64ui-p-bltu
rv64ui-p-bne
rv64ui-p-fence_i
rv64ui-p-jal
rv64ui-p-jalr
rv64ui-p-lb
rv64ui-p-lbu
rv64ui-p-ld
rv64ui-p-lh
rv64ui-p-lhu
rv64ui-p-lui
rv64ui-p-lw
rv64ui-p-lwu
rv64ui-p-ma_data
rv64ui-p-or
rv64ui-p-ori
rv64ui-p-sb
rv64ui-p-sd
rv64ui-p-sh
rv64ui-p-simple
rv64ui-p-sll
rv64ui-p-slli
rv64ui-p-slliw
rv64ui-p-sllw
rv64ui-p-slt
rv64ui-p-slti
rv64ui-p-sltiu
rv64ui-p-sltu
rv64ui-p-sra
rv64ui-p-srai
rv64ui-p-sraiw
rv64ui-p-sraw
rv64ui-p-srl
rv64ui-p-srli
rv64ui-p-srliw
rv64ui-p-srlw
rv64ui-p-sub
rv64ui-p-subw
rv64ui-p-sw
rv64ui-p-xor
rv64ui-p-xori
rv64ui-v-add
rv64ui-v-addi
rv64ui-v-addiw
rv64ui-v-addw
rv64ui-v-and
rv64ui-v-andi
rv64ui-v-auipc
rv64ui-v-beq
rv64ui-v-bge
rv64ui-v-bgeu
rv64ui-v-blt
rv64ui-v-bltu
rv64ui-v-bne
rv64ui-v-fence_i
rv64ui-v-jal
rv64ui-v-jalr
rv64ui-v-lb
rv64ui-v-lbu
rv64ui-v-ld
rv64ui-v-lh
rv64ui-v-lhu
rv64ui-v-lui
rv64ui-v-lw
rv64ui-v-lwu
rv64ui-v-ma_data
rv64ui-v-or
rv64ui-v-ori
rv64ui-v-sb
rv64ui-v-sd
rv64ui-v-sh
rv64ui-v-simple
rv64ui-v-sll
rv64ui-v-slli
rv64ui-v-slliw
rv64ui-v-sllw
rv64ui-v-slt
rv64ui-v-slti
rv64ui-v-sltiu
rv64ui-v-sltu
rv64ui-v-sra
rv64ui-v-srai
rv64ui-v-sraiw
rv64ui-v-sraw
rv64ui-v-srl
rv64ui-v-srli
rv64ui-v-srliw
rv64ui-v-srlw
rv64ui-v-sub
rv64ui-v-subw
rv64ui-v-sw
rv64ui-v-xor
rv64ui-v-xori
rv64um-p-div
rv64um-p-divu
rv64um-p-divuw
rv64um-p-divw
rv64um-p-mul
rv64um-p-mulh
rv64um-p-mulhsu
rv64um-p-mulhu
rv64um-p-mulw
rv64um-p-rem
rv64um-p-remu
rv64um-p-remuw
rv64um-p-remw
rv64um-v-div
rv64um-v-divu
rv64um-v-divuw
rv64um-v-divw
rv64um-v-mul
rv64um-v-mulh
rv64um-v-mulhsu
rv64um-v-mulhu
rv64um-v-mulw
rv64um-v-rem
rv64um-v-remu
rv64um-v-remuw
rv64um-v-remw

是通过spike 进行测试的.
跑测结果

1
2
# 对所有测试项进行测试, 会通过Makefile中的指定对应的isa 进行测试
make run

例子
编译 执行

1
2
3
4
5
# 编译
riscv64-unknown-elf-gcc -march=rv64g -mabi=lp64 -static -mcmodel=medany -fvisibility=hidden -nostdlib -nostartfiles -DENTROPY=0xcaa8519 -std=gnu99 -O2 -I./../env/v -I./macros/scalar -T./../env/v/link.ld ./../env/v/entry.S ./../env/v/*.c rv64ui/lui.S -o rv64ui-v-lui
# 执行
# 执行结果放在 *.out 文件中, 无log为正常现象
spike --isa=rv64gc_zfh_zicboz_svnapot rv64ui-v-lui 2> rv64ui-v-lui.out

benchmarks

跑测执行

1
2
3
4
5
6
7
8
9
10
11
12
make run
# 例子 qsort 编译
...
riscv64-unknown-elf-gcc -I./../env -I./common -I./median -I./qsort -I./rsort -I./towers -I./vvadd -I./multiply -I./mm -I./dhrystone -I./spmv -I./mt-vvadd -I./mt-matmul -I./pmp -DPREALLOCATE=1 -mcmodel=medany -static -std=gnu99 -O2 -ffast-math -fno-common -fno-builtin-printf -fno-tree-loop-distribute-patterns -o qsort.riscv ./qsort/qsort_main.c ./common/syscalls.c ./common/crt.S -static -nostdlib -nostartfiles -lm -lgcc -T ./common/test.ld

# 执行
spike --isa=rv64gc qsort.riscv > qsort.riscv.out
# 结果
cat qsort.riscv.out
mcycle = 127363
minstret = 127368
...

qsort 主要源码:

1
2
3
4
5
6
setStats(1);  // 计时开始
sort( DATA_SIZE, input_data ); //对 dataset1.h 存的 input_data 进行排序
setStats(0); // 计时结束, 输出时间

// Check the results
return verify( DATA_SIZE, input_data, verify_data ); // 判断排序结果是否和 预期的 dataset1.h 存的 verify_data 一致, 不一致, 退出case报错

跑分测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
├── Makefile
├── common # 公共源文件, 启动相关
│ ├── crt.S
│ ├── syscalls.c
│ ├── test.ld
│ └── util.h
├── dhrystone
│ ├── dhrystone.c
│ ├── dhrystone.h
│ └── dhrystone_main.c
├── median
│ ├── dataset1.h # 存放预期的执行结果
│ ├── median.c
│ ├── median.h
│ ├── median_gendata.pl
│ └── median_main.c
├── mm
│ ├── common.h
│ ├── gen.scala
│ ├── mm.c
│ ├── mm_main.c
│ └── rb.h
├── mt-matmul
│ ├── dataset.h # 存放预期的执行结果
│ ├── matmul.c
│ ├── matmul_gendata.pl
│ └── mt-matmul.c
├── mt-vvadd
│ ├── dataset.h # 存放预期的执行结果
│ ├── mt-vvadd.c
│ ├── vvadd.c
│ └── vvadd_gendata.pl
├── multiply
│ ├── dataset1.h
│ ├── multiply.c
│ ├── multiply.h
│ ├── multiply_gendata.pl
│ └── multiply_main.c
├── pmp
│ └── pmp.c
├── qsort
│ ├── dataset1.h # 存放预期的执行结果
│ ├── qsort_gendata.pl
│ └── qsort_main.c
├── qsort.riscv.out.tags
├── readme.txt
├── rsort
│ ├── dataset1.h
│ └── rsort.c
├── spmv
│ ├── dataset1.h # 存放预期的执行结果
│ ├── spmv_gendata.scala
│ └── spmv_main.c
├── towers
│ └── towers_main.c
└── vvadd
├── dataset1-large.h # 存放预期的执行结果
├── dataset1.h
├── vvadd_gendata.pl
└── vvadd_main.c

mt

矩阵乘法相关, 编译报错, 不是重点, 暂时跳过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
├── Makefile
├── ad_matmul.c
├── ae_matmul.c
├── af_matmul.c
├── ag_matmul.c
├── ai_matmul.c
├── ak_matmul.c
├── al_matmul.c
├── am_matmul.c
├── an_matmul.c
├── ap_matmul.c
├── aq_matmul.c
├── ar_matmul.c
├── at_matmul.c
├── av_matmul.c
├── ay_matmul.c
├── az_matmul.c
├── bb_matmul.c
├── bc_matmul.c
├── bf_matmul.c
├── bh_matmul.c
├── bj_matmul.c
├── bk_matmul.c
├── bm_matmul.c
├── bo_matmul.c
├── br_matmul.c
├── bs_matmul.c
├── ce_matmul.c
├── cf_matmul.c
├── cg_matmul.c
├── ci_matmul.c
├── ck_matmul.c
├── cl_matmul.c
├── cm_matmul.c
├── crt.o
├── cs_matmul.c
├── cv_matmul.c
├── cy_matmul.c
├── dc_matmul.c
├── df_matmul.c
├── dm_matmul.c
├── do_matmul.c
├── dr_matmul.c
├── ds_matmul.c
├── du_matmul.c
├── dv_matmul.c
├── mt-vvadd.o
├── syscalls.o
├── vvadd0.c
├── vvadd0.o
├── vvadd1.c
├── vvadd2.c
├── vvadd3.c
└── vvadd4.c

sifive freedom-e-sdk

https://github.com/sifive/freedom-e-sdk

目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
❯ tree -L 1 -d
.
├── FreeRTOS-metal #FreeRTOS 相关
├── bsp # board 相关
├── doc
├── docker
├── freedom-devicetree-tools # device tree 编译相关
├── freedom-metal # baremental 相关
├── pip-cache # python 环境相关
├── scl-metal # software crypto库
├── scripts # 编译 执行相关脚本
├── software # baremental app
└── venv # python 环境相关

software case

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
❯ tree -L 1 -d
.
├── atomics
├── cflush
├── clic-hardware-vector-interrupts
├── clic-nested-interrupts
├── clic-nonvector-interrupts
├── clic-selective-vector-interrupts
├── coremark
├── csr
├── dhrystone
├── empty
├── example-buserror
├── example-freertos-blinky
├── example-freertos-minimal
├── example-freertos-pmp-blinky
├── example-hca-metal
├── example-hello
├── example-hpm
├── example-i2c
├── example-itim
├── example-l2pf
├── example-l2pm
├── example-lim
├── example-pmp
├── example-privilege-level
├── example-pwm
├── example-remapper
├── example-rnmi
├── example-rtc
├── example-spi
├── example-user-mode
├── example-user-syscall
├── example-vm-test
├── example-watchdog
├── hello
├── local-interrupt
├── local-vector-interrupts
├── mem-latency
├── minimal-boot
├── multicore-hello
├── plic-interrupts
├── return-fail
├── return-pass
├── sifive-welcome
├── software
├── software-interrupt
├── test-coreip
├── timer-interrupt
├── uart-interrupt
└── vm-test

基础编译

1
make TARGET=${target} PROGRAM=${program}

引用的文件

头文件:
PLATFORM_HEADER = bsp/qemu-sifive-s51/metal-platform.h
MACHINE_HEADER = bsp/qemu-sifive-s51/metal.h
MACHINE_INLINE = bsp/qemu-sifive-s51/metal-inline.h

march=rv64imac -mabi=lp64

ld: bsp/qemu-sifive-s51/metal.default.lds

freedom-metal/src/ 下的文件会编译为 bsp/qemu-sifive-s51/install/lib/debug/libmetal.a
freedom-metal/gloss/ 下的文件编译为 bsp/qemu-sifive-s51/install/lib/debug/libmetal-gloss.a

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
libmetal_a_SOURCES = \
src/drivers/fixed-clock.c \
src/drivers/fixed-factor-clock.c \
src/drivers/inline.c \
src/drivers/riscv_clint0.c \
src/drivers/riscv_cpu.c \
src/drivers/riscv_plic0.c \
src/drivers/sifive_remapper2.c \
src/drivers/sifive_buserror0.c \
src/drivers/sifive_ccache0.c \
src/drivers/sifive_clic0.c \
src/drivers/sifive_fe310-g000_hfrosc.c \
src/drivers/sifive_fe310-g000_hfxosc.c \
src/drivers/sifive_fe310-g000_lfrosc.c \
src/drivers/sifive_fe310-g000_pll.c \
src/drivers/sifive_fe310-g000_prci.c \
src/drivers/sifive_global-external-interrupts0.c \
src/drivers/sifive_gpio-buttons.c \
src/drivers/sifive_gpio-leds.c \
src/drivers/sifive_gpio-switches.c \
src/drivers/sifive_gpio0.c \
src/drivers/sifive_i2c0.c \
src/drivers/sifive_l2pf0.c \
src/drivers/sifive_l2pf1.c \
src/drivers/sifive_local-external-interrupts0.c \
src/drivers/sifive_pl2cache0.c \
src/drivers/sifive_prci0.c \
src/drivers/sifive_pwm0.c \
src/drivers/sifive_rtc0.c \
src/drivers/sifive_spi0.c \
src/drivers/sifive_test0.c \
src/drivers/sifive_trace.c \
src/drivers/sifive_uart0.c \
src/drivers/sifive_simuart0.c \
src/drivers/sifive_wdog0.c \
src/drivers/ucb_htif0.c \
src/remapper.c \
src/atomic.c \
src/button.c \
src/cache.c \
src/clock.c \
src/cpu.c \
src/entry.S \
src/scrub.S \
src/trap.S \
src/gpio.c \
src/hpm.c \
src/i2c.c \
src/init.c \
src/interrupt.c \
src/led.c \
src/lock.c \
src/memory.c \
src/pmp.c \
src/prci.c \
src/privilege.c \
src/pwm.c\
src/rtc.c \
src/shutdown.c \
src/spi.c \
src/switch.c \
src/synchronize_harts.c \
src/timer.c \
src/time.c \
src/trap.S \
src/tty.c \
src/uart.c \
src/vector.S \
src/watchdog.c

libmetal_gloss_a_SOURCES = \
gloss/crt0.S \
gloss/nanosleep.c \
gloss/sys_access.c \
gloss/sys_chdir.c \
gloss/sys_chmod.c \
gloss/sys_chown.c \
gloss/sys_clock_gettime.c \
gloss/sys_close.c \
gloss/sys_execve.c \
gloss/sys_exit.c \
gloss/sys_faccessat.c \
gloss/sys_fork.c \
gloss/sys_fstat.c \
gloss/sys_fstatat.c \
gloss/sys_ftime.c \
gloss/sys_getcwd.c \
gloss/sys_getpid.c \
gloss/sys_gettimeofday.c \
gloss/sys_isatty.c \
gloss/sys_kill.c \
gloss/sys_link.c \
gloss/sys_lseek.c \
gloss/sys_lstat.c \
gloss/sys_open.c \
gloss/sys_openat.c \
gloss/sys_read.c \
gloss/sys_sbrk.c \
gloss/sys_stat.c \
gloss/sys_sysconf.c \
gloss/sys_times.c \
gloss/sys_unlink.c \
gloss/sys_utime.c \
gloss/sys_wait.c \
gloss/sys_write.c
# software 下的源文件
software/minimal-boot/minimal-boot.c

跑测

1
./scripts/test-qemu-targets

scripts/test-qemu-targets

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#TARGETS=(qemu-sifive-e31 qemu-sifive-s51 qemu-sifive-u54 qemu-sifive-u54mc )
TARGETS=(qemu-sifive-s51 )
PROGRAMS=(minimal-boot )

for target in ${TARGETS[@]} ; do
for program in ${PROGRAMS[@]} ; do

build_output_file=$( mktemp -p ./ tmp.${target}.${program}.build.XXXXXXXXXX)
run_output_file=$( mktemp -p ./ tmp.${target}.${program}.run.XXXXXXXXXX)

case $program in
hello) expected_output="Hello, World!";;
minimal-boot) expected_output="minimal-boot";;
*) expected_output="";;
esac

>&2 echo "Building ${program} on ${target}"

make TARGET=${target} PROGRAM=${program} 2>&1 | tee ${build_output_file}
timeout --foreground ${TIMEOUT_SECONDS}s bash -c "make TARGET=${target} PROGRAM=${program} simulate 2>/dev/null | tee ${run_output_file}"

if [ ! -f ${run_output_file} -o `cat ${run_output_file} | grep -c "${expected_output}"` -eq 0 ] ; then
>&2 echo "${program} on ${target} failed to produce the expected output"
else
>&2 echo "${program} on ${target} passed"
fi

# Make sure we clean up after ourselves
make TARGET=${target} PROGRAM=${program} clean 2>&1 >/dev/null
done

跑测命令:

1
scripts/simulate --elf /home/liguang/program/riscv-lab/freedom-e-sdk/software/minimal-boot/debug/minimal-boot.elf --qemu qemu-system-riscv64 --qemu-config bsp/qemu-sifive-s51/qemu.cfg

实际跑测命令:

1
qemu-system-riscv64 -M sifive_e -kernel /home/liguang/program/riscv-lab/freedom-e-sdk/software/minimal-boot/debug/minimal-boot.elf -nographic

build benchmark

Building an Benchmark Program

Building a benchmark program is slightly special in that certain section is required to be loaded in specific memory region. A specialize linker file has been created for its optimal run.

1
make PROGRAM=dhrystone TARGET=coreip-e31-arty LINK_TARGET=ramrodata software

Debugging a Target Program

1
make [PROGRAM=hello] [TARGET=sifive-hifive1] [CONFIGURATION=debug] debug

test_page

测试 2-stage translation 页表的 PTE的集合
vs 为 vs-stage translation 的pte
h 为 g-stage translation 的pte

1
2
3
4
5
6
7
struct {
uint64_t vs;
uint64_t h;
} test_page_perm_table [] = {
# index ----------- vs ---------------------- h ------#
[VSRWX_GRWX]      =   {PTE_V | PTE_RWX,         PTE_V | PTE_RWX},
}

页表属性按照PTE的 7:0 位设置相关的bit位

image-20211126175029553

hspt_init

测试框架执行 hspt_init 对2-stage 页表进行初始化
如 tinst_tests 测试项, 该测试集对指令进行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
-+ tinst_tests
\ -+ hspt_init
\ - addr = 0x00000000;
| -+ for(int i = 0; i < 4; i++)
\ - hspt[0][i] = PTE_V | PTE_AD | PTE_RWX | (addr >> 2); "设置第一级页表 pgd"
| - addr += PGSIZE << (2 * 9) "设置完后, hspt[0][0] -> hspt[0][3] 为 0 0x4000000 0x80000000 0xc0000000 base 地址"
| - hspt[0][4] = PTE_V | (((uintptr_t)&hspt[1][0]) >> 2); "设置PPN 二级页表"
| - hspt[1][0] = PTE_V | (((uintptr_t)&hspt[2][0]) >> 2); "设置 PPN 三级页表"
| - addr = TEST_PPAGE_BASE; "0x88000000"
| -+ for(int i = 0; i < TEST_PAGE_MAX; i++) "TEST_PAGE_MAX = 512, 一共设置512个PTE"
\ -+ hspt[2][i] = (addr >> 2) | PTE_AD | test_page_perm_table[i].vs;
"设置PTE 物理地址为 0x88000000 - 0x881ff000 / 间隔 1page"
"每个PTE 对应的测试集的权限不同, 取自 test_page_perm_table.vs 权限集"
| - addr += PAGE_SIZE;
| - CSRW(satp, satp); "M-mode 或 Hs-mode 下设置satp 为 hspt 基地址即 hspt[0][0]的基址"
| - goto_priv(PRIV_HS); "进入hs-mode"
| - uintptr_t vaddr_f = hs_page_base(VSI_GI); "VSI_GI=100, vs_page_base 0x100000000 + 100* PGSIZE = 0x100064000"
"访问不了, 应该报错"
| - TEST_SETUP_EXCEPT(); "初始化 except 数据 为 0"
| - value = lb(vaddr_f); "lb load vaddr_f 的数据"
| - TEST_ASSERT(excpt.cause == CAUSE_LPF) "测试应该陷入 M-mode handler, 且mcause 应为 13 load page fault"
" 如果未触发异常, 或mcause 不对, case 报错"

大概回顾下页表结构
可见这个是针对sv39 的页表
回顾下 sv39 页表的查表方法

sv39 in RV64:

当在 satp 寄存器中启用了分页时,S 模式和 U 模式中的虚拟地址会以从根部遍历页表的方式转换为物理地址。

  • satp.PPN 给出了一级页表的基址, VA[38:30]给出了一级页号, 因此处理器会读取位于地址(satp.PPN × 4096 + VA[38:30] × 8)的页目录项
  • 该 PTE 包含二级页表的基址, VA[29:21]给出了二级页号, 因此处理器读取位于地址(PTE.PPN × 4096 + VA[29:21] × 8)的页目录项
  • 该 PTE 包含了三级页表的基址, VA[20:12] 给出了三级页号, 处理器读取位于地址(PTE.PPN × 4096 + VA[20:12] × 8)的页目录项
  • 该页表项的 PTE. PPN 就是物理地址对应的 PPN * 4096 + offset 得到物理地址

PTE 的 [0-9] 位为权限位, [10-53] 的 44 位为 PPN.

sv39 上. 虚拟地址范围 39 位, 0-38 位 , 物理地址范围 56位, 0-55 位

#页表

hpt_init

此项为设置G-stage translation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
void hpt_init(){

for(int i = 0; i < 2048; i++){
hpt_root[i] = 0;
}

uintptr_t addr = 0x0;
for(int i = 0; i < 4; i++){
// 设置 hpt_root[0/1/2/3] 大页 addr 为 0x0 0x4000_0000 0x8000_0000 0x10000_0000
hpt_root[i] =
PTE_V | PTE_U | PTE_AD | PTE_RWX | (addr >> 2);
addr += SUPERPAGE_SIZE(0);
}
// 覆盖 hpt_root[2], 该页变为 页表项 指向 hpt[0][0]
hpt_root[MEM_BASE/SUPERPAGE_SIZE(0)] =
PTE_V | (((uintptr_t)&hpt[0][0]) >> 2);

addr = MEM_BASE; // (0x8000_0000)
for(int i = 0; i < 512; i++) hpt[0][i] = 0;
for(int i = 0; i < MEM_SIZE/SUPERPAGE_SIZE(1)/2; i++){ // i < 64
hpt[0][i] =
PTE_V | PTE_U | PTE_AD | PTE_RWX | (addr >> 2); // 设置 2M的PTE 大页
addr += SUPERPAGE_SIZE(1); //0x20_0000 //2M
}
// 覆盖 hpt_root[4] 其变为页表项 2级页表, 指向 hpt[1][0] 3级页表
hpt_root[4] =
PTE_V | (((uintptr_t)&hpt[1][0]) >> 2);

hpt_root[2047] =
PTE_V | (((uintptr_t)&hpt[1][0]) >> 2);

// hpt[1][0] 的3级页表 指向 hpt[2][0] 的pte
hpt[1][0] =
PTE_V | (((uintptr_t)&hpt[2][0]) >> 2);

hpt[1][511] =
PTE_V | (((uintptr_t)&hpt[2][0]) >> 2);

addr = TEST_PPAGE_BASE; //0x8800_0000 开始的物理地址
// 设置hpt[2][0] 到 hpt[2][511] 的PTE, 映射的物理地址从0x8800_0000 开始
for(int i = 0; i < TEST_PAGE_MAX; i++){
hpt[2][i] = (addr >> 2) | PTE_AD |
test_page_perm_table[i].h;
addr += PAGE_SIZE;
}

// hpt[1][1] 的3级页表 指向 hpt[3][0] 的pte
hpt[1][1] =
PTE_V | (((uintptr_t)&hpt[3][0]) >> 2);
addr = TEST_PPAGE_BASE; //0x8800_0000 开始的物理地址
// 设置hpt[3][0] 到 hpt[3][511] 的PTE, 映射的物理地址从0x8800_0000 开始
for(int i = 0; i < 512; i++){
hpt[3][i] = (addr >> 2) |
PTE_V | PTE_U | PTE_AD | PTE_RWX;
addr += PAGE_SIZE;
}

// hpt_root[5] 为2级页表项, 指向 hpt[4][0]
hpt_root[5] =
PTE_V | (((uintptr_t)&hpt[4][0]) >> 2);
addr = TEST_PPAGE_BASE; // 0x8800_0000
// hpt[4][0] - hpt[4][511] 为 大页 PTE, 每个PTE 覆盖 2M 范围
for(int i = 0; i < 512; i++){
hpt[4][i] = (addr >> 2) |
PTE_V | PTE_U | PTE_AD | PTE_RWX;
addr += SUPERPAGE_SIZE(1); // +2M
}

if(curr_priv == PRIV_HS || curr_priv == PRIV_M){
uintptr_t hsatp = (((uintptr_t)hpt_root) >> 12) | (0x8ULL << 60);
CSRW(CSR_HGATP, hsatp);
} else {
ERROR("trying to set hs hgatp from lower privilege");
}
}

再来看下 vs-stage的页表创建过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
void vspt_init(){

uintptr_t addr;

addr = 0x00000000;
for(int i = 0; i < 4; i++){
vspt[0][i] =
PTE_V | PTE_AD | PTE_RWX | (addr >> 2);
addr += SUPERPAGE_SIZE(0);
}

vspt[0][MEM_BASE/SUPERPAGE_SIZE(0)] = //vspt[0][2] 2级 -> vspt[1][0] 3级
PTE_V | (((uintptr_t)&vspt[1][0]) >> 2);

addr = MEM_BASE;
for(int i = 0; i < 512; i++) vspt[1][i] = 0;
// vspt[1][0] - vspt[1][63] 建立大页, 覆盖地址从 0x8000_0000 到 0x8000_0000 + 2M * 64 地址范围
for(int i = 0; i < MEM_SIZE/SUPERPAGE_SIZE(1)/2; i++){
vspt[1][i] =
PTE_V | PTE_AD | PTE_RWX | (addr >> 2);
addr += SUPERPAGE_SIZE(1);
}

// vspt[0][4] 2级 -> vspt[2][0] 3级
vspt[0][4] =
PTE_V | (((uintptr_t)&vspt[2][0]) >> 2);

// vspt[0][5] =
// PTE_V | PTE_U | PTE_AD | (((uintptr_t)&vspt[2][0]) >> 2);

// vspt[2][0] 3级页表 -> vspt[3][0] PTE
vspt[2][0] =
PTE_V | (((uintptr_t)&vspt[3][0]) >> 2);

addr = TEST_VPAGE_BASE; // 0x10000_0000 PTE
// vspt[3][0] -> vspt[3][511] PTE GPA 地址范围 0x10000_0000 -- 0x10000_0000 + 2M
for(int i = 0; i < TEST_PAGE_MAX; i++){
vspt[3][i] = (addr >> 2) | PTE_AD |
test_page_perm_table[i].vs;
addr += PAGE_SIZE;
}
// vspt[2][1] 3级 -> vspt[4][0]
vspt[2][1] =
PTE_V | (((uintptr_t)&vspt[4][0]) >> 2);

addr = 4 * SUPERPAGE_SIZE(0) + SUPERPAGE_SIZE(1); // 0x10000_0000 + 2M
// vspt[4][0] - vspt[4][511] PTE 地址范围 0x10000_0000 + 2M -- 0x10000_0000 + 4M
for(int i = 0; i < 512; i++){
vspt[4][i] = (addr >> 2) |
PTE_V | PTE_AD | PTE_RWX;
addr += PAGE_SIZE;
}

// vspt[0][5] 二级页表 -> vspt[5][0] 三级页表
vspt[0][5] =
PTE_V | (((uintptr_t)&vspt[5][0]) >> 2);

// vspt[5][0] - vspt[5][511] 为大页 PTE , 覆盖的地址范围为 0x14000_0000 - 0x14000_0000+ 2M*512
addr = 5 * SUPERPAGE_SIZE(0); // 0x14000_0000 5G
for(int i = 0; i < 512; i++){
vspt[5][i] = (addr >> 2) |
PTE_V | PTE_AD | PTE_RWX;
addr += SUPERPAGE_SIZE(1);
}

uintptr_t satp = (((uintptr_t)vspt) >> 12) | (0x8ULL << 60);
if(curr_priv == PRIV_VS){
CSRW(satp, satp);
} else if(curr_priv == PRIV_HS || curr_priv == PRIV_M){
CSRW(CSR_VSATP, satp);
} else {
ERROR("");
}
}
1
2
3
4
uintptr_t addr1 = phys_page_base(SWITCH1); // 0x8800_0000 + 108 * 4k
uintptr_t addr2 = phys_page_base(SWITCH2); // 0x8800_0000 + 109 * 4k
uintptr_t vaddr1 = vs_page_base(SWITCH1); // 0x10000_0000 + 108 * 4k
uintptr_t vaddr2 = vs_page_base(SWITCH2); // 0x10000_0000 + 109 * 4k

先看 vs-stage 的翻译, 0x10000_0000 + 108 * 4k
首先 satp = vsatp. PPN = vspt >> 12, 对应sv39模式,

  • va[38:30] (9位) 给出了一级页号偏移为 4, 在一级页表的物理页中找到二级页表的物理页号 vspt[0][4]
  • va[29:21] (9位) 给出了二级页号偏移为 0, 在二级页表的物理页中找到三级页表的物理页号 vspt[2][0]
  • va[20:12] (9位) 给出了三级页号偏移为 108, 在三级页表的物理页中找到要访问位置的物理页号 vspt[3][108]
  • 物理页号对应的物理页基址(即物理页号左移12位)加上 offset 就是虚拟地址对应的物理地址
    vspt[3][108].ppn << 12 + 0 (offset) = 0x10000_0000 + 108*4k + 0

所以 vs-stage 0x10000_0000 - 0x10000_0000 + 4M 的范围是直接映射的.

再来看G-stage的翻译过程, GPA为 0x10000_0000 + 108 * 4k
对应于 sv39x4, hgatp.PPN 给出了一级页表首地址

  • GPA[40:30] (11位) 给出了一级页号偏移为 4, 在一级页表的物理页中找到二级页表的物理页号 hpt_root[4]
  • GPA[29:21] (9位) 给出了二级页号偏移为 0, 在二级页表的物理页中找到三级页表的物理页号 hpt[1][0]
  • GPA[20:12] (9位) 给出了三级页号偏移为 108, 在三级页表的物理页中找到要访问位置的物理页号 hpt[2][108]
  • 物理页号对应的物理页基址(即物理页号左移12位)加上 offset 就是虚拟地址对应的物理地址 , 对应的权限是 test_page_perm_table[108].h=PTE_U | PTE_RWX
    hpt[2][108].ppn << 12 + 0 (offset) = 0x8800_0000 + 108*4k + 0

翻译后, addr1 与 vaddr1 正好是对应的 HPA 与 GVA的映射关系

再来看下 second_stage_only_translation 的测试项:

1
vs_page_base_limit(TOP) = 0x1fffffff000  "即 0x20000_0000 前的最后一个 2M "

由于vstap 写了 0, guest os 未启用分页, 所以guest os 直接访问的就是GPA
对应于 sv39x4, hgatp.PPN 给出了一级页表首地址

  • GPA[40:30] (11位) 给出了一级页号偏移为 2047 (11个1), 在一级页表的物理页中找到二级页表的物理页号 hpt_root[2047]
  • GPA[29:21] (9位) 给出了二级页号偏移为 511 (9个1), 在二级页表的物理页中找到三级页表的物理页号 hpt[1][511]
  • GPA[20:12] (9位) 给出了三级页号偏移为 511 (9个1), 在三级页表的物理页中找到要访问位置的物理页号 hpt[2][511]
  • 物理页号对应的物理页基址(即物理页号左移12位)加上 offset 就是虚拟地址对应的物理地址 , 对应的权限是 test_page_perm_table[511].h=PTE_U | PTE_RWX
    hpt[2][511].ppn << 12 + 0 (offset) = 0x8800_0000 + 511*4k + 0

最后看下VS-mode下可以切换页表, 而页表都是落在原始地址范围 hpt 0x80020000 hspt 0x80032000 vspt 0x8002c000
先看下 vs-stage的转换 0x80020000

首先 satp = vsatp. PPN = vspt >> 12, 对应sv39模式,

  • va[38:30] (9位) 给出了一级页号偏移为 2, 在一级页表的物理页中找到二级页表的物理页号 vspt[0][2]
  • va[29:21] (9位) 给出了二级页号偏移为 0, 在二级页表的物理页中找到三级页表的物理页号 vspt[1][0], 此处为大页, 所以页目录walk 终止.
  • va[20:12] (9位) 和 va[11:10] 最终给出了页内偏移为 0x20000, 最终的GPA 地址为
    vspt[1][0].ppn << 12 << 9 + 0x20000 = 0x8000_0000 + 0x20000

同理GVA-GPA的映射关系为 0x80032000 - 0x80032000 0x8002c000 - 0x8002c000
所以 0x8000_0000 - 0x8000_0000+64*0x200000 (0x8800_0000) 范围是线性映射的, GVA等同于GPA

再来看G-stage的转换 0x80020000

对应于 sv39x4, hgatp.PPN 给出了一级页表首地址

  • GPA[40:30] (11位) 给出了一级页号偏移为 2, 在一级页表的物理页中找到二级页表的物理页号 hpt_root[2]
  • GPA[29:21] (9位) 给出了二级页号偏移为 0, 在二级页表的物理页中找到三级页表的物理页号 hpt[0][0], 此处为大页, 所以页目录walk 终止.
  • GPA[20:12] (9位) 和 GPA[11:10] 最终给出了页内偏移为 0x20000, 最终的HPA 地址为
    hpt[0][0].ppn << 12 << 9 + 0x20000 = 0x8000_0000 + 0x20000

同理GPA-HPA的映射关系为 0x80032000 - 0x80032000 0x8002c000 - 0x8002c000
所以 0x8000_0000 - 0x8000_0000+64*0x200000 (0x8800_0000) 范围是线性映射的, GPA等同于HPA

最终 VS-mode下可以切换 hs vs g-stage 的页表项, 页表项所处的地址范围 GVA 和 HPA 是线性映射的, GVA等同于 HPA.

调研 qemu 和 spike 的代码, G-stage 的页表项必须带有 PTE_U 的 flag, G-stage 才能不导致 load/write exception.

rvh h-extension 1.0

image-20240416112451638

  • sstatus 反映 Supervisor-mode(后面简称 S-mode) 下的cpu 状态, 这里仅以其 SPP(Supervisor previous previlege) 做介绍, 如第一次想从S-mode 执行到 User-mode(下简称U-mode) 下, 需要设置sstatus.SPP=U-mode (0)
  • satp (Supervisor Address Translation and Protection (satp) Register), 保存了第一级页表地址
  • sepc (Supervisor Exception Program Counter) 保存发生trap/中断 前的 pc 地址, 此处如为第一次执行该用户态应用, 则需要将其设置为用户态的入口地址
  • sret (S-mode return), 通俗来讲就是调用sret后, 硬件会设置 pc = spec, 下一条指令从sepc 的位置执行. 除此之外, sret 还会判断 sstatus.SPP 确定下一刻要将cpu 置为哪个特权模式, 此处如为第一次执行用户态应用, 需要将sstatus.SPP 置为 U-mode, 下一刻cpu会切换特权级, 从S-mode 切换到 U-mode.
  • scause S-mode 原因寄存器, 在发生异常/中断时, scause 会被硬件修改对应的位, 表明当前进入异常/中断的原因.

虚拟机 os,需要完整的运行环境。
我们考虑一个简单的linux运行需要什么环境

  • 1,通用寄存器,传参,sp,pc这些。
  • 2,CSR,比如status,cause寄存器这些,配置cpu的运行状态,或者获取cpu的运行状态等。
  • 3,内存,需要管理虚拟地址到物理地址的转换。
  • 4,trap handler,发生异常或者中断时的处理方法。
  • 5,特权等级。

这些东西,通过纯软件也可以做模拟,但是效率和安全性上都会受影响,RVH ISA就是在CPU 硬件层面上给这些虚拟机OS运行环境需要的资源进行了明确的要求和定义。

HS-模式的作用与S-模式相同,但有额外的指令和CSR,2-stage translation, 翻译GVA->HPA, 为guest os 提供内存和mmio的外设地址空间模拟, 常规的S模式操作系统可以在HS模式或VS模式下执行,无需修改。

特权模式

image-20240416112455653

Hypervisor and Virtual Supervisor CSRs

这些csr 有两类:

  • h开头的csr 供hypervisor 管理程序 vm 使用
  • vs 开头的寄存器, 给guest os 使用. 可以看作是S-mode 专属csr的备份
    为什么需要vs* 的csr, cpu 可以切换普通的进程, 也可以在普通进程和vcpu 之间进行调度, 如果没有vs* 的csr, 那进程和vcpu的上下文就非常的混乱.

在HS模式下运行的操作系统或管理程序使用s-mode CSR与异常、中断和地址转换子系统进行交互
H-extension 定义了额外的CSR提供给HS模式,但不提供给VS模式,以管理 2-stage 地址翻译 和控制VS-mode guest os的行为:hstatus, hedeleg, hiddenleg, hvip, hip, hie, hgeip, hgeie, henvcfg, henvcfgh, hcounteren, htimedelta, htimedeltah, htval, htinst, and hgatp。

当V=1时,VS CSRs代替了相应的s-mode的CSRs,接管了通常监督器CSRs的所有功能。S-mode CSR的指令应改为访问相应的VS CSR。当V=1时,试图通过自己的独立CSR地址直接读取或写入VS CSR会导致虚拟指令异常。VS CSRs本身只能从M模式或HS模式访问。

一些标准 S-mode CSR(senvcfg、scounteren和scontext …)没有匹配的VS CSR, 这些csr的行为在V=1 和 V=0 时都是一样的, 这意味着hypervisor 需要保存恢复这些csr的状态.

hstatus

image-20240416112459089

  • 当VTSR=1时,VS模式中执行SRET的尝试会引起一个虚拟指令异常。
  • 当VTW=1时(并假设mstatus.TW=0),如果WFI没有在特定的时间限制内完成,VS模式中执行WFI的尝试会引起一个虚拟指令异常。
  • 当VTVM=1时,VS模式中执行SFENCE.VMA或SINVAL.VMA或访问CSR satp的尝试会引起一个虚拟指令异常。

VGEIN (Virtual Guest External Interrupt Number) 在中断直通场景下使用, 对于支持aia imsic 的中断控制器, 用于选择一个 virtual guest external interrupt file
当VGEIN=0 时, 不选择 virtual guest external interrupt file, 表示是物理cpu的中断.

HU (Hypervisor in U-mode), HU位允许管理程序的一部分在U模式下运行,以更好地保护软件错误,同时仍然保留对虚拟机的访问。

HU=1, HLV, HLVX, and HSV 可以在 HS-mode 和 U mode下执行
HU=0, 在U-mode下执行 HLV, HLVX, and HSV 会触发非法指令异常

SPV 每当陷阱进入HS模式时,SPV位(进入 HS-mode 以前的模式)就会被写入。hstatus中的SPV位在陷阱时被设置为虚拟化模式V的值。如 V=0时执行SRET指令,SPV被设置为 0。

hedeleg hideleg

中断代理
medeleg mideleg 相应位被设置后, S-mode 可以接管 部分异常/中断
同理 hedeleg hideleg 相应位被设置后, VS-mode 可以接管 HS-mode的部分异常/中断

hideleg
如果一个被委托给HS模式的中断(使用mideleg)被进一步委托给VS模式, 在hideleg的15:0位中,10、6和2位(对应于标准的VS级中断)是可写的,12、9、5和1位(对应于标准的S级中断)是只读零。

通常
virtual supervisor external interrupt (10)
virtual supervisor timer interrupt (6)
virtual supervisor software interrupt (2)

对于中断注入和中断直通来说, 上述 10/6/2 bit位是必须被设置的.
VS-mode下 guest os接收到这些中断后, 10/6/2 会被转换位 9/5/1 号中断

这样是为了保持 guest os可以使用普通的kernel版本, 不需要再为guest 修改中断相关的源码

一个被委托给HS模式的同步陷阱(使用medeleg)被进一步委托给VS模式,如果在陷阱之前V=1,并且相应的hedeleg位被设置。hedeleg的每一位都应是可写的或只读的零。

image-20240416112502300

hvip hip hie vsip vsie hgeip hgeie

上述均为管理 guest os 相关的中断相关的csr

image-20240416112506239
hvip

Setting VSEIP in hvip asserts a VS-level external interrupt;
setting VSTIP asserts a VS-level timer interrupt;
setting VSSIP asserts a VS-level software interrupt.

image-20240416112511051
hip

vsip hip hvip 之间的关系如下:
hideleg 开启时(bit 10/6/2 不为0时)

  • vsip.SEIP = hip.VSEIP vsip.STIP = hip.VSTIP vsip.SSIP = hip.VSSIP
    • VSEIP is read-only in hip, and is the logical-OR of these interrupt sources:
      • bit VSEIP of hvip;
      • bit of hgeip selected by hstatus.VGEIN
      • any other platform-specific external interrupt signal directed to VS-level.
    • VSTIP is read-only in hip, and is the logical-OR of:
      • hvip.VSTIP
      • any other platform-specific timer interrupt signal directed to VS-level.
    • VSSIP in hip is an alias (writable) of the same bit in hvip.
      • 即 vsip.SSIP = hip.VSSIP = hvip.VSSIP
  • vsie.SEIE = hie.VSEIE vsie.STIE = hie.VSTIE vsie.SSIE = hie.SSIE

真正影响guest os的是vsip , guest os 运行时处于V=1 mode, vsip被替换为sip,

  • vsip software 中断来自于hvip (来自于HS hypervisor), 或者guest os 自己.
  • vsip timer 中断来自于hvip (来自于 HS hypervisor), 或guest os 自己, 或 platform-specific timer interrupt signal directed to VS-level
  • vsip guest external 中断来自于hvip (来自于HS hypervisor), 或hstatus.VGEIN -> hgeip 或 any other platform-specific external interrupt signal directed to VS-level

hgeip read-only, 由硬件置位, 在支持中断直通的中断控制器上, 由中断控制器进行置位, 代表来自于中断控制器的哪个virtual guest interrupt file. 简单来讲是表示直通的哪个vcpu收到了中断.

Bits hip.SGEIP and hie.SGEIE are the interrupt-pending and interrupt-enable bits for guest external interrupts at supervisor level (HS-level).
hip.SGEIP 和 hip.SGEIE 是给HS 用的, 重点看 SGEIP, 这个是hgeip & hgeie and的结果, 代表的有guest external 中断待处理.

henvcfg henvcfgh

image-20240416112515434

FIOM (Fence of I/O implies Memory)
FIOM=1 and V=1 时, 原子指令访问的区域排序为设备I/O,其aq和/或rl位被设置,那么该指令的排序就像它同时访问设备I/O和内存一样。

image-20240416112518813

PBMTE = 1, Svpbmt extension is available for VS-stage address translation
PBMTE=0, Svpbmt extension is not implemented for VS-stage address translation

STCE位的定义将由即将到来的Sstc扩展提供。它在henvcfg中的意义可能会在批准该扩展之前发生变化
CBZE位的定义将由即将到来的Zicboz扩展提供。它在henvcfg中的分配可能会在批准该扩展之前发生变化。
CBCFE和CBIE位的定义将由即将到来的Zicbom扩展提供。它们在henvcfg中的分配可能会在批准该扩展之前发生变化。

Register henvcfgh does not exist when HSXLEN=64

hcounteren

Hypervisor Counter-Enable Register
控制硬件性能监测计数器对客户虚拟机的可用性

image-20240416112522067

V=1时, 当 CY、TM、IR或HPMn位未设置时,试图读取cycle、time、instret或 hpmcountern寄存器会触发虚拟指令异常, 即使在 mcouteren 中对应的bit位 为1时.

当这些位被设置了, VS-mode下才能读取对应的寄存器.
在VM-mode下, 需要hcounteren 和 scounteren 的对应位都被设置才可以读取对应的寄存器

htimedelta

Hypervisor Time Delta Registers

读取VS或VU模式下的time CSR,返回的是 htimedelta + time csr。

htval

因异常进入HS-mode时,htval与stval一起被写入额外的特定异常信息,以协助软件处理陷阱。
(htval被写入GPA, stval 被写入GVA)

  • 发生 guest-page-fault trap 时, htval 会被写入异常的 GPA >> 2
  • 其他异常进入的trap , htval = 0
  • 未来可能会为其他异常修改htval的行为

当VS阶段的翻译未能完成时 (在第一阶段的GVA->GPA 发生异常时), 此时GPA是未知的。CSR htinst中提供了额外的信息来消除这种情况的歧义。

其他情况下, 对于未对齐的地址load/store 引发的 guest page fault, htval 会被写入对应的GPA.
对于具有可变长度指令的系统上的指令异常,非零的htval对应于指令的异常部分,如stval中的虚拟地址所示。

htinst

因异常进入HS-mode时, htinst 被写入了触发异常的指令, 以协助软件处理trap

hgatp

Hypervisor Guest Address Translation and Protection Register

image-20240416112525892

image-20240416112528370

MODE=Bare, VS-mode的 物理地址等于S-mode下的物理地址,除了物理内存保护外,对GPA没有进一步的内存保护。

image-20240416112531453

2 more bits at the high end in VPN[2]

对于实现39位虚拟地址的机器(Sv39),这使得管理程序扩展可以支持多达41位的客户物理地址空间,而不需要硬件支持48位的虚拟地址(Sv48)或回落到使用影子页表模拟更大的地址空间。

VMID (guest os id) 的位数不是固定的, VMIDLEN 在64位中 最多 14位

如果hgatp 发生了改变, 需要执行HFENCE.GVMA 刷新G-stage 页表

v开头的csr

vsstatus vsip vsie vstvec vsscratch vsepc vscause vstval vsatp

不过多介绍, 行为同S-mode的 s开头的csr 行为一致

虚拟化H扩展定义了一个硬件状态位,称作V状态,可以为0或1,V状态不同,定义和访问的CSR寄存器也不同。

  • 当V为0时
    • 以“s”开头的CSR寄存器表示当前操作系统的状态
    • “h”开头的用于支持和实现虚拟化软件
    • “vs”开头的代表运行在虚拟化技术上的系统状态。
  • 当V为1时
    • “s”开头的寄存器指向了前文以“vs”开头的寄存器。

Hypervisor Instructions

Hypervisor Virtual-Machine Load and Store Instructions

image-20240416112535307

管理程序虚拟机的加载和存储指令只在M模式或HS模式下有效,或者在hstatus.HU=1时在U模式下有效。
每条指令都像V=1一样执行显式内存访问;
也就是说,具有地址转换和保护,以及适用于VS模式或VU模式的内存访问的endianness。
hstatus.SPVP控制访问的权限级别。

  • 当SPVP=0时,显式内存访问在VU模式下完成,
  • 而当SPVP=1时,在VS模式下完成。
    当V=1时,应用两阶段地址转换,HS sstatus.SUM被忽略。HS sstatus.MXR 使地址翻译的两个阶段(VS阶段和G阶段)都可以读到只有执行权限的page,而vsstatus.MXR只影响第一个翻译阶段(VS阶段)

LB、LBU、LH、LHU、LW、LWU和LD,都有一个相应的虚拟机加载指令。
HLV.B, HLV.BU, HLV.H, HLV.HU, HLV.W, HLV.WU, 和HLV.D。
对于每条RV32I或RV64I存储指令,SB、SH、SW和SD,都有一个相应的虚拟执行指令
当然,指令HLV.WU、HLV.D和HSV.D对RV32是无效的。

指令HLVX.HU HLVX.WU 与 HLV.HU和HLV.WU相同,只是在地址转换过程中,执行权限取代了读权限。
也就是说,在地址转换的两个阶段有执行权限的页都有读权限。
对于由地址转换产生的HS-mode的HPA,其需要具有执行和读权限.

在VS-mode 或 VU-mode 下执行这些指令会触发虚拟指令异常
hstatus.HU=0时, 在U-mode下执行这些指令会触发指令异常

Hypervisor Memory-Management Fence Instructions

image-20240416112538467

HFENCE.VVMA和HFENCE.GVMA执行的功能与SFENCE.VMA(第4.2.1节)类似
只是 HFENCE.VVMA 适用于由CSR vsatp 控制的VS-stage translation (GVA->GPA)
HFENCE.GVMA 适用于 CSR hgatp 控制的 G-stage translation (GPA->HPA)。
指令SFENCE.VMA只适用于由当前satp控制的 1-stage translation

特殊的, 在guest os 中执行sfence.vma 适用于 vs-stage translation

HFENCE.VVMA is valid only in M-mode or HS-mode

执行HFENCE.VVMA保证任何已经对当前 hart 可见的先前存储在所有隐式读取之前被排序,该hart 为VS阶段地址转换所做的指令为

  • HFENCE.VVMA

HFENCE.GVMA 在 M-mode下有效或
当mstatus.TVM=0时,HFENCE.GVMA在HS模式下有效

排序指令, 执行 HFENCE.GVMA 后, 先前执行的存储指令对于 排在 HFENCE.GVMA 后面的隐式内存读取指令是可见的

Machine-Level CSRs

h-extension 引入后修改了 mstatus、mideleg、mip和mie,并增加了CSRs mtval2和mtinst。

mstatus

增加了两个bit MPV GVA

当因异常进入 m-mode时, mpv (Machine Previous Virtualization Mode) 被设置
mret执行时,硬件状态位 V 被设置为 MPV,除非MPP=3,在这种情况下V仍然是0。

当因异常进入 m-mode时, GVA (Guest Virtual Address) 被设置
因这些异常 (breakpoint, address misaligned, access fault, page fault, or guest-page fault)
mtval 被写入 guest virtual address, GVA is set to 1.
For any other trap into M-mode, GVA is set to 0.

修改了 TVM MPRV (Modify PRiVilege)
TVM=1时, HS-mode 不能访问hgatp, 不能执行 HFENCE.GVMA 和 HINVAL.GVMA

MPRV=0, translation and protection behave as normal, using the translation and protection mechanisms of the current privilege mode

当MPRV=1时,显式内存访问被翻译和保护,endianness被应用

image-20240416112542233

mideleg

h-extension 加入后, 其10/6/2 变成了只读
如果 guest external interrupts(GEILEN) 不为0时, 12 bit 也变成只读, 意味着这种情况下 VS-level的interrupts 总是被委托给 VS-mode

mideleg 中bit位是0的, 在 hideleg hip hie的相应bit是只读的

mip mie

SGEIP, VSEIP, VSTIP VSSIP in mip 等同于 hip的相应bit位
SGEIE, VSEIE, VSTIE VSSIE in mie 等同于 hie的相应bit位

mtval2

Machine Second Trap Value Register
在发生异常进入 M-mode后, mtval2 被写入额外的 异常特定的信息

  • guest-page-fault 异常, mtval2 要么是0 要么是 GPA >> 2, 对于其他异常, 总是为0, 未来可能被扩展
    如果是因为 vs-stage 发生的 guest-page-fault, gpa是无效的, 需要借助 mtinst 进行辨别

对于地址未对齐导致的 guest page fault, mtval2中的非零GPA 对应于mtval中的虚拟地址所表示的访问的page fault部分。
对于变长地址指令, mtval2 对应于 mtval 表示的虚拟地址 page fault部分.

mtinst

Machine Trap Instruction Register

在发生异常进入 M-mode 后, 提供发生异常的指令信息

2-stage translation

vs-stage vsatp GVA->GPA
G-stage hgatp GPA->HPA

G-stage translation

G阶段地址翻译是Sv32、Sv39、Sv48或Sv57的通常基于页面的虚拟地址翻译方案的一个变种。在每种情况下,传入地址的大小都会扩大2比特(到34、41、50或59比特)。为了适应2个额外的位,根页表(仅)被扩展了4倍,即16KiB,而不是通常的4KiB。

G-stage 阶段发生 page fault 时, 触发的异常是 guest-page-fault exceptions
所有G阶段PTE中的G位被保留用于以后进行扩展

G-stage 的pte 的权限同样会被检查, 如果是因权限问题导致的page fault, 异常原因是 instruction, load, or store/AMO guest page fault

Guest-Page Faults

该类异常通常会被委托给HS-mode, 需要设置medeleg 的对应bit. 如不进行委托, 则有m-mode 进行处理.
发生异常时, mtval 或 stval 会写入发生page fault时的 GVA.
mtval2 或 htval 会写0或GPA>>2.
CSR mtinst或htinst 会被写入 page fault时的指令信息

当一条指令获取或非对齐的内存访问跨越了一个页面边界时,会涉及两个不同的地址转换。在这种情况下发生客户页面故障时,写入mtval/stval的fault 虚拟地址与普通page fault所需的相同。因此,如果该页边界的字节在被访问的字节之间,page fault 的 虚拟地址可能是一个高于指令的原始虚拟地址的页边界地址。

当发生该异常时, 是由于 VS-stage 阶段导致的, 写到 mtval2/htval的GPA 等同于 写到mtval/stval的VA 地址.

Memory-Management Fences

讨论 HFENCE.VVMA 和 HFENCE.GVMA 的使用
见前文

因修改PMP 导致影响到了2-stage 页表所在的物理地址或页表所指向的物理地址的情况下
M-mode 的软件修改了PMP的设置后, 需要同步给虚拟内存子系统

对于HS-mode的软件所使用的虚拟地址, M-mode 下可以通过sfence.vma 0 0 刷新页表

hfence.gvma 0 0 刷新所有的G-stage 和 VS-stage 的页表缓存
防止错误使用了 被修改的 pmp 设置影响到的物理地址对应的 缓存的页表项 .

Traps

Trap Cause Codes

H-extension加入后, cause中添加了:

VS级中断(中断2、6、10)、S-mode 级guest external interrupt(中断12)、虚拟指令异常(22)和 guest page fault(20、21、23。此外,来自VS模式的 ecall 调用 被指定为10,而来自HS-mode 或S-mode的ecall 调用则像往常一样使用原因9。

#cause #riscv_cause scause mcause

image-20240416112546720

当V=1时,如果尝试的指令是HS legal的,但由于权限不足或由于HS-mode CSR(如scounteren或hcounteren)明确禁用该指令而被阻止执行的, 通常会触发一个虚拟指令异常(代码22),而不是非法指令异常.
如果一条指令在HS模式下执行是有效的(对于指令寄存器操作数的某些值),假设CSR mstatus的字段TSR和TVM都是零,那么它就是HS legal 的。

具体来说,在以下情况下触发一个虚拟指令异常:

  • in VS-mode, attempts to access a counter CSR when the corresponding bit in hcounteren is 0 and the same bit in mcounteren is 1;
  • in VU-mode, attempts to access a counter CSR when the corresponding bit in either hcounteren or scounteren is 0 and the same bit in mcounteren is 1;
  • in VS-mode or VU-mode, attempts to execute a hypervisor instruction (HLV, HLVX, HSV, or HFENCE);
  • in VS-mode or VU-mode, attempts to access an implemented hypervisor CSR or VS CSR when the same access (read/write) would be allowed in HS-mode, assuming mstatus.TVM=0;
  • in VU-mode, attempts to execute WFI when mstatus.TW=0, or to execute a supervisor instruction (SRET or SFENCE);
  • in VU-mode, attempts to access an implemented supervisor CSR when the same access (read/write) would be allowed in HS-mode, assuming mstatus.TVM=0;
  • in VS-mode, attempts to execute WFI when hstatus.VTW=1 and mstatus.TW=0, unless the instruction completes within an implementation-specific, bounded time;
  • in VS-mode, attempts to execute SRET when hstatus.VTSR=1;
  • in VS-mode, attempts to execute an SFENCE.VMA or SINVAL.VMA instruction or to access satp, when hstatus.VTVM=1.

对RISC-V特权架构的其他扩展可能会增加当V=1时导致虚拟指令异常的情况集合。

image-20240416112551526
h-extension 加入后的 同步异常优先级

如果一条指令可能引起多个同步异常,表8.7中优先级递减的顺序表明哪个异常被采取并以mcause或scause报告。

Trap Entry

当陷阱发生在VS模式或VU模式时,它将进入M模式,除非由medeleg或mideleg委托,在这种情况下,它将进入HS模式,除非由hedeleg或hideleg进一步委托,在这种情况下,它将进入VS模式。

未设置委托, MPV and MPP in mstatus 根据进入trap/中断 前的模式设置为对应的值
image-20240416112555070

如果设置了委托 mideleg medeleg, trap / 中断 进 HS-mode前会将 sstatus的SPV SPP 设置为对应的值
image-20240416112558910

当进一步设置了 hideleg hedeleg, trap/中断 进 VS-mode 前讲 vsstatus SPP 设置为对应值
V 始终为1
image-20240416112602991

mtinst htinst

当因为 trap 进入 M-mode 或 HS-mode时
mtinst 或 htinst 被更新为:

  • 0
  • trap 时的 instruction
  • 自定义值 (trapping instruction 不是标准的)
  • 特定的伪指令

除非需要一个伪指令值,否则写入 mtinst 或 htinst 的值可能总是零,这表明硬件在寄存器中没有为这个特定的陷阱提供任何信息

写入陷阱指令 CSR 的值有两个目的。

  • 首先是提高陷阱处理程序中指令仿真的速度,部分是通过允许处理程序跳过从内存中加载陷阱指令,部分是通过避免一些解码和执行指令的工作。
  • 第二个目的是通过伪指令提供关于由 VS 阶段地址转换的隐式内存访问引起的 guest page fault 的额外信息。

在中断时,写入陷阱指令寄存器的值总是零。

表8.11显示了每个标准异常原因可能被自动写入陷阱指令寄存器的值。对于阻止获取指令的例外情况,可以只写零或一个伪指令值。只有trapping 的指令是非标准的,才能自动写入一个自定义值。未来的标准或扩展可能允许其他值被写入,从先前建立的允许值集合中选择。

image-20240416112606153

同步异常可以向陷阱指令寄存器写入捕获指令的标准转换,只针对由显式内存访问(来自加载、存储和AMO指令)产生的异常。目前只为这些内存访问指令定义了标准转换。
如果一个没有定义转换的标准指令发生了同步陷阱,陷阱指令寄存器应以零写入(或者在某些情况下,以特殊的伪指令值写入)。

对于一个标准的压缩指令(16位大小),转换后的指令如下:

  • 转换为32位等效指令。
  • bit 1 被替换为 0

对于guest page fault,在以下情况下,陷阱指令寄存器被写入一个特殊的伪指令值。
(a) 该故障是由VS阶段地址转换的隐式内存访问引起的,
(b) 一个非零值(GPA)被写入mtval2或htval。
如果这两个条件都满足,写入mtinst或htinst的值必须取自下表;不允许为零。

image-20240416112609501

伪指令 0x00003020 用于硬件试图自动更新VS级页表中的位A和/或D的情况。
所有其他用于VS阶段地址转换的隐式内存访问将被读取。
如果硬件从未自动更新VS级页表中的位A或D(将此留给软件),那么写入伪指令的情况就不会出现。这样的页表更新实际上必须是原子性的,而不仅仅是一个简单的写

Trap Return

MRET指令被用来从一个进入M模式的陷阱返回。MRET首先根据mstatus.MPP mstatus.MPV 中的值确定新的特权模式是什么,然后MRET 设置 mstatus MPV=0,MPP=0,MIE=MPIE,和MPIE=1。最后,MRET设置先前确定的特权模式,并设置pc=mepc。

SRET指令用于从进入HS模式或VS模式的陷阱返回。它的行为取决于当前的虚拟化模式。

当以M模式或HS模式执行时(即V=0),SRET首先根据 hstatus.SPV 和 sstatus.SPP 中的值确定新的特权模式是什么,然后SRET设置hstatus.SPV=0,在sstatus中设置SPP=0,SIE=SPIE,和SPIE=1。最后,SRET设置先前确定的特权模式,并设置pc=sepc。

当以VS模式执行时(即V=1),SRET根据 vsstatus.SPP 设置特权模式,在vsstatus设置SPP=0、SIE=SPIE和SPIE=1,最后设置pc=vsepc。

image-20240416112612609

  • hstatus cpu 虚拟模式状态寄存器, 此处关注的是其 SPV位, 该位决定 sret 运行后将cpu 的V状态, V=0 还是 V=1
  • sstatus (S-mode) cpu 运行状态寄存器, 此处 host os 和 guest os 都用到了该csr, sret 返回时, 需要综合判断 hstatus.SPV 和 sstatus.SPP, 来确定下一刻cpu 从当前的HS-mode 返回到 VS-mode/HS-mode/U-mode/VU-mode
  • hgatp (Hypervisor Guest Address Translation and Protection Register), 用于G-stage 页表翻译, 需要将其设置为guest os的G-stage 的第一级页表基址
  • hideleg/hedeleg (Hypervisor Trap Delegation Registers) hypervisor 中断/异常委托寄存器, 此处只需要在第一次进入guest os 时设置
  • stvec S-mode 下中断/异常向量入口地址
  • htval hinst 用于判断导致异常时发生的异常指令. 如G-stage page fault, hinst 反映了发生异常时执行的什么指令, htval 表示发生异常时GPA的地址
  • mmio 地址触发的G-stage page fault, 一般表明是模拟外设的地址空间出现了page fault, 此类异常一般会由用户态的管理程序(qemu)进行模拟
  • sret 根据hstatus.SPV 和 sstatus.SPP 位决定返回 HS-mode/U-mode/VS-mode/VU-mode 中的哪一个.
  • scause S-mode 原因寄存器, 在发生异常/中断时, scause 会被硬件修改对应的位, 表明当前进入异常/中断的原因.

History

0 2017/5/7
0.1-draft 2017/11/10
0.2-draft 2018/10/15
0.2 2018/12/3
0.3 2019-03-06
0.4 2019/6/17
0.5 2019/10/30
0.6 2020/2/9
0.6.1 2020/5/6
0.6.2 2021/7/31
1.0.0-rc 2021/9/16
1.0.0 2021/11/30
1.0 2021/12/1

1.0 -> now

  • Add FLH, FSH to defined transformed instructions for H extension
  • Clarify that henvcfg.PBMTE is read-only zero if Svpbmt is not implemented
  • 其他一些细微的修改, 如文档描述换个说法表达等

0.6.1 -> 1.0 差异

1.0 新增

  • hgatp 新增 sv57x4 mode
  • henvcfg (hypervisor environment configuration)
  • 其他一些细微的修改, 如文档描述换个说法等.

riscv 硬件虚拟化

riscv qemu-kvm 框架

image-20240416112618610

arm vs riscv 硬件虚拟化

image-20240416112621851

riscv-aia

riscv 体系下, 中断直通需要 支持 aia 架构的 riscv-imsic

https://github.com/riscv/riscv-aia/

riscv-imsic stable
image-20240416112625946

riscv-imsic release
image-20240416112629030

riscv-iommu

设备直通需要 iommu 组件, riscv 体系下, iommu 属于 non-isa 部分.

Non-ISA specifications do not add new instructions, create or change opcodes, or in any way modify the RISC-V ISA. They do help us to develop an ecosystem around the ISA Specifications.

https://github.com/riscv-non-isa/riscv-iommu/

image-20240416112632130

问题1:

boston 使用的 pcie 总线 xinlinx_pcie_root_realize 在调用 pci_bridge_initfn 后, qemu系统中查询 pci_bus 失败

object_resolve_path_type(“”, TYPE_PCI_BUS, NULL)

导致qemu 模拟的iommu 设备无法正常注册

riscv_iommu_sys_realize 函数中不能找到 pci_bus,没有走 riscv_iommu_pci_setup_iommu 函数。

bus->iommu_fn 为 空

问题2:

正常情况下 board 注册完时,会通过 notify 调用 pci_done

最终, 会调用 bus->iommu_fn 函数

riscv_iommu_find_as->riscv_iommu_space->memory_region_init_iommu(as->iova_mr, TYPE_RISCV_IOMMU_MEMORY_REGION)

address_space_init(&as->iova_as, MEMORY_REGION(&as->iova_mr), TYPE_RISCV_IOMMU_PCI)

而 boston board 没有走这条链路。
这个链路是 qemu guest 起来之前走的,因此与 dts 配置无关。

问题 3:

boston 上添加 gpex 总线
原来的 xinlinx 总线还是会影响到 pci_bus,导致不能正常初始化 iommu 设备
需要删除 xinlinx 总线的创建。

问题 4:

e1000e 网卡没有与 iommu 进行交互

gpex 总线的 dts 配置中需要设置 iommu-map
第一项代表 rid-base,对应 pcie 设备的总线号 15-8 位
第二项表示 iommu 的 phandle
第四项代表 length, match rid-base:rid-base+length 范围内的 pcie 设备
e1000e 网卡的总线号 0000 在该范围内,系统起来后,会将 e1000e 网卡加到 iommu group 中。

问题 5:

复制 virt 的 pcie dts 配置后, e1000e 网卡中断不正常。
调研后发现是 interrupt-map 配置有问题。

interrupt-map
<child-address , child-id parent-phandle, 中断号>

virt 的中断控制器 plic 配置的 interrupt-cells 为 1, 即只有 <中断号>

到了 boston 上, parent aplic_s 的 interrupt-cells 为 2,代表 <中断号,中断类型>
所以 interrupt-map 应该配置为
<child-address , child-id parent-phandle, 中断号,中断类型>
第一项占位取决与 address-cells

问题 6:

vfio 配置,VFIO_SET_IOMMU 需要打开 allow_unsafe_interrupts 选项,否则 group 会 attach 不上 container

问题 7

在 close(group_fd) 或 VFIO_IOMMU_UNMAP_DMA 时会包 folio 的 panic
原因为 unmap_dma 时会调用 riscv_iommu_iova_to_phys 接口翻译 iova->phys
在该函数中,翻译时取页内偏移时调用了 iova & PAGE_MASK,而 PAGE_MASK 是 ~(1<<12 -1), 这个地方应该 & ~PAGE_MASK, 才是取页内偏移。由于计算物理地址有误,导致了后续的物理地址 unpin 错误。

SPEC

Feature

iommucap register

  • S bit: 支持 2-stage 地址翻译
  • A bit: 支持 AIA
  • MRF: 支持 imsic 的 MRF 模式

iommucapen register
E bit , E=1 时, iommu 开启.

device table walk

  • RSID (Requester Source ID) 设备源ID
  • RSIDHI
  • RSIDLO

device table entry

dtbase 寄存器, 保存 device table 的首地址.

  • 当 rsiddiv 不为 0 时, device table 有两级
  • 当 rsiddiv 为 0 时, device table 只有一级.

在 rsiddiv 不为 0 时
第一级的 device table entry 结构如下:


第二级的 device table entry 结构如下:


在 rsiddiv 为 0 时, 只有一级 device table entry, 也如上图 (图 6) 结构所示

  • V 有效位, V=0, 关闭该设备的地址翻译.
  • S 控制是否开启 2-stage 地址翻译
  • F 控制是否开启 1-stage 地址翻译
  • Addr 指向 translation descriptor

device table walk 流程

1 级 device table walk.

When the rsiddiv is zero, the IOMMU uses only one level of device table. 012

2 级 device table walk

When rsiddiv is not zero, the IOMMU uses two levels of device table 013

Register-based Device Table

如果 device table 不是以 dtbase 实现的, 即非内存驻留的方式.
而是 iommu 提供地址寄存器 dte[x], x< 2^RSIDLEN-1, 该方式只支持一级 device table entry.

  • P 位为 1 时, iommu 在引起 walk fault 时暂停该次翻译, P = 0, iommu 终止该次翻译.
  • V 有效位
  • Addr 指向 Translation Descriptor

Translation Descriptor

地址翻译描述符

128-191 位主要作用是保留 RSID. 并对 device table walk 和 address table walk 发生 fault 时的行为进行配置

Bit 0-1 Bit 2-3 Configuration Bits 控制 walk fault 行为


Bit 4 为 1 时, 1-stage device table walk 总是使用 Pause mode
Bit 5 为 1 时, 1-stage address table walk 总是使用 Pause mode
如果 device table entry 的 S bit 为 0, Bit 4 Bit 5 被忽略.

当 desc.S2MODE != 0 时, 43-0 位为 GPA 的页号

If the S2MODE field is not zero, bits 0-63 contains the 4K-aligned physical page number of the register MMIO page for the IOMMU allocated by the hypervisor. 011

当 desc.S2MODE = 0 时, 63-0 位为 1-stage 的控制域

Address Translation

Translation Descriptor 在内存中.

除了进行地址转换外,IOMMU 还根据相关地址表项中的权限位向源设备授予权限。
如果请求的访问类型没有在表项中被授予,IOMMU 就会报告一个错误情况。
由各个设备协商并在 IOMMU 翻译表中设置适当的权限。例如,由设备驱动来配置权限.

IOMMU 支持两个阶段的地址转换。两个翻译阶段都可以独立地启用和禁用。
当第一阶段转换被启用时,操作系统通常使用它将设备分配给各个进程。
当启用第二阶段转换时,管理程序通常使用它将设备分配给虚拟机。
虚拟机可以进一步启用第一阶段转换,以控制设备对虚拟机内进程的分配。

1-stage 地址翻译

当 desc.S2MODE = 0 时, 进行 1-stage 地址转换. 根据 dtbase 找到 device table, 根据 RSID 找到对应的 device table entry, 根据 desc 的 63-0 的 S1PPN 找到一级页表的 pgd. 根据 63-0 的 S1MODE 模式进行地址翻译, 翻译过程同 mmu.

2-stage 地址翻译

当 desc.S2MODE 不为 0 时, 进行 2-stage 地址翻译.

根据末级 device table entry 的 F/S 的设备确定是只进行 G-stage 翻译还是需要进行 G-stage 和 S-stage 翻译.

如果只进行 G-stage (S 位为 1, F 位为 0), 则 RPAddr 为 GPA pgd. 继而根据 desc.S2MODE 进行页表翻译, 翻译过程同 mmu

如果要进行 2-stage 翻译, 则以下表的流程? 没看懂, desc 在 S2MODE 打开时, 只有一个 PRAddr 地址, 这个 PRAddr 指向的是什么? #TODO


根据对 simple code 的解读, 可以先大概看下 2-stage 地址翻译的过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// rsiddiv 不为0 时, 为两级 device table
dtbase = dtbase_reg;
rsiddiv = rsiddiv_reg;
rsid_hi = rsid >> rsiddiv;
rsid_lo = rsid & ((1 << rsiddiv) - 1);
lv1_dte_addr = dtbase + (rsid_hi << 3); // 保存了第一级 device table entry 的地址
lv1_dte = memory_load8(lv1_dte_addr);
lv2_dte_addr = lv1_dte + (rsid_lo << 3); // 保存了第二级 device table entry 的地址
out_dte = memory_load8(lv2_dte_addr); // 第二级device_table_entry 的内容 |Addr|F|S|V|
if (out_dte & DTE_V_BIT)
*output_addr = input_addr;
u64 desc_addr = out_dte & ~0x7; //取出 Translation Desc
dev_conf = memory_load8(desc_addr + 16); //desc 191-128 位 为 device configuration 配置
s1_atp = memory_load8(desc_addr); // desc 63-0 位为 第一级 vs-stage satp
s2_atp = memory_load8(desc_addr + 8); // desc 127-64 位为 第二级 G-stage hgatp
viommu_reg_addr = memory_load8(desc_addr + 24); // ? desc 255-192位 保存 guest_dtbase GPA
viommu_reg_addr &= ~RPADDR_MASK;
guest_dtbase = memory_load8(viommu_reg_addr);
guest_dtbase_phys = riscv_one_stage(s2_atp, guest_dtbase, ACC_READ, &res, &exp_addr); //通过s2_atp hgatp 将 guest_dtbase GPA -> guest_dtbase_phys HPA
guest_rsid = (memory_load8(desc_addr + 128) & (0 xFFFFFFFFUL << 32)) >> 32; // guest_rsid 保存在 desc_addr + 128 字节的位置

------------- 嵌套进行 下一级翻译 ----------------------------
dtbase = guest_dtbase_phys[3];
rsiddiv = guest_dtbase_phys[1];
rsid_hi = guest_rsid >> rsiddiv;
rsid_lo = guest_rsid & ((1 << rsiddiv) - 1);
lv1_dte_addr = dtbase + (rsid_hi << 3);
lv1_dte_addr_phys = riscv_one_stage(s2_atp, lv1_dte_addr, ACC_READ, &res, &exp_addr)
lv1_dte = memory_load8(lv1_dte_addr_phys);
lv2_dte_addr = lv1_dte + (rsid_lo<< 3);
lv2_dte_addr_phys = riscv_one_stage (s2_atp , lv2_dte_addr , ACC_READ , &res , &exp_addr);
// 到该级只有 1-stage, 该处末级 device table entry 的 F位为1, S位必须为0.
out_dte = memory_load8(lv2_dte_addr_phys);
desc_addr = out_dte & ~0x7;
dev_conf_phys = riscv_one_stage(s2_atp, desc_addr + 16, ACC_READ, &res, &exp_addr);
s1_atp_phys = riscv_one_stage(s2_atp, desc_addr, ACC_READ, &res, &exp_addr);
s1_atp = memory_load8(s1_atp_phys);
*output_addr = riscv_two_stages(s1_atp, s2_atp, input_addr, access_type, &res, &exp_addr); // 最终才进行2-stage 地址翻译.

通过上述 simple code 的解析, 大概和上图的流程基本一致.
为什么设计的这么麻烦, 需要两次 device table walk 的流程?
第二次 device table walk 过程中, 所涉及的地址还全是 GPA, 需要 s2_atp 进行地址翻译到 HPA, 才能进行下一步翻译.

Fault Reporting

支持两种模式

  • pause mode
    在 fault 上报之前暂停翻译, 软件处理 fault, 软件可以选择终止翻译事务还是恢复翻译事务, 如软件选择了恢复 (映射好对应的页表), iommu 硬件可以恢复该翻译事务.
    iommu 的具体行为取决于其在哪个阶段触发了 fault.
    • device table walk 过程中触发了 fault, iommu 只能终止该事务
    • address table walk 过程中触发了 fault, iommu 可以根据 desc 128-191 中的 Bit 0-1 2-3 4 5 bit 的配置选择恢复还是 abort 该事务.
  • abort mode 终止地址翻译并将 fault 上报给软件

寄存器

iommuinten.E 控制是否启用 iommu 的中断.

同一时刻可能有多个异常产生, 对应多个进程可能发生的 fault 异常.

iommuintno 中断号

如果 iommu 支持 AIA, Translation Descriptor 被扩展, 增加到 Bit 384 位宽.

iommucause 原因寄存器

ftval 寄存器提供 fault 的地址

resume 寄存器, pause mode 下使用

  • T = 1 代表恢复, T=0 代表终止事务
    T=1 时, iommu 会重新开始该次翻译事务.

中断发送

IOMMU 可能设计用于两种类型的中断系统,传统 PLIC 和 AIA。

当使用 PLIC 时,当支持 2-stage 时,不提供单独的中断。
当使用 IMSIC 时, iommu 可以直接将中断分发给对应的 vcpu, 这可以直接通过写 imsic 的 setipnum 来实现.

TLB

提供 invltlb RSID 刷新 RSID 对应设备的 tlb entry.

排序一致性

IOMMU 对于特定的 RSID 翻译事务保持顺序, 当该 RSID 的某个翻译事务触发 fault 时, 排在其后面的翻译事务只能等待, 直到该事务被终止或恢复完成. 因此需要按 RSID 来将事务缓存到 buffer 中的能力.

处理该错误可能导致 IOMMU 或软件直接取消事务,在这种情况下,IOMMU 对具有相同 RSID 的所有后续事务返回错误。

IOPMP

IOMMU 与 IOPMP 相互补充,对恶意设备的恶意 DMA 请求提供深度防御。
当前 IOMMU 的设计完全控制了授予每个设备的权限。然而,当设备表的 V 位被清除时,设备获得对物理内存的直接访问。此外,IOMMU 的未来版本预计将与 PCI-Express 的地址翻译服务(ATS)一起工作。ATS 允许设备将 DMA 请求标记为“已翻译”,以便 IOMMU 简单地将请求传递给互连。这是一个潜在的安全漏洞,因为恶意设备总是可以将任何 DMA 请求标记为“已翻译”。在这种情况下,IOMMU 可以与指定给定设备允许的物理地址范围的 IOPMP 配对,即使请求被标记为已翻译,IOPMP 也能够拒绝访问。

SPEC

Device Table


IOMMU Register Interface

TLB 操作时才会用到下面这些寄存器

IOMMU EntryLo0 and EntryLo1 Register Format

physical frame number (物理地址页号) 35-6 共 30bit.

PFNX PFN[39:36] stored in EntryLo0/EntryLo1 bits 35:32
PFN PFN[35:12] stored in EntryLo0/EntryLo1 bits 29:6

IOMMU EntryHi Register

VPN 虚拟地址页号 39-13 共 27 bit

VPNU Bits 39:32 of vpn
VPN2 Bits 31:13 of vpn

PageMask Register

The PageMask register in the IOMMU is required to define the page size of a TLB entry.
指定 TLB entry 的 page size.

Mask 32:13 共 20 位

掩码字段是一个位掩码,其中逻辑“1”表示虚拟地址的对应位不参与 TLB 匹配。请注意,只有有限范围的 PageMask 值是合法的(即,用“1”从低位向上填充 PageMaskMask 字段,每次两个)。最大页面大小为4 GB。
mask 32-13 12-0 (page ) 为 0
0x11 16k 2^2 page 不参与地址匹配
0x1111 64k 2^4 page 不参与地址匹配
0x111111 256k
… …
0xFFFFF 4G

IOMMU Global Configuration Register

Global Configuration register (GCFG)

  • DVNUM 一共多少个 device table entry
  • VZEN 是否启用 iommu 的虚拟化
  • IE 是否打开 iommu 的中断
  • PW 是否实现了 Page Walker, 该字段总是 0, 表明 mips iommu 不支持 page walker
  • EN 是否启用 iommui

TLB Commands

  • write
    支持 TLB 的 Index 和 Random 写。EntryLo0, EntryLol, EntryHi 和 PageMask 数据寄存器必须在写命令本身执行之前写入。
  • Read
    EntryLo0, EntryLol, EntryHi 和 PageMask 数据寄存器在执行 TLB 读命令时加载 TLB 表项的内容。对 EntryLo0, EntryLol, EntryHi 和 PageMask 寄存器的读取将数据返回到通用寄存器(GPRs)。
  • Probe
    该命令探测是否存在与 EntryHi 和 PageMask 寄存器的内容匹配的条目。
  • Invalidate
    Execution of the invalidate command invalidates any entry that matches EntryHi.GuestID.
  • Invalidate Flush.
    Execution of the invalidate flush command invalidates all entries in the IOMMU TLB.

IOMMU Segmentation Control Register

包含 segctl0/segctl1/segctl2 寄存器
如果 cpu 是 64 位的, 则不会用到这三个寄存器

Configuration of the memory segmentation system when the P6600 core is in 32-bit EVA mode.